/**
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#if ENABLE(WML)
#include "WMLInputElement.h"
#include "Attribute.h"
#include "EventNames.h"
#include "FormDataList.h"
#include "Frame.h"
#include "HTMLNames.h"
#include "KeyboardEvent.h"
#include "RenderTextControlSingleLine.h"
#include "TextEvent.h"
#include "WMLDocument.h"
#include "WMLNames.h"
#include "WMLPageState.h"
namespace WebCore {
WMLInputElement::WMLInputElement(const QualifiedName& tagName, Document* doc)
: WMLFormControlElement(tagName, doc)
, m_isPasswordField(false)
, m_isEmptyOk(false)
, m_wasChangedSinceLastChangeEvent(false)
, m_numOfCharsAllowedByMask(0)
{
}
PassRefPtr<WMLInputElement> WMLInputElement::create(const QualifiedName& tagName, Document* document)
{
return adoptRef(new WMLInputElement(tagName, document));
}
WMLInputElement::~WMLInputElement()
{
if (m_isPasswordField)
document()->unregisterForDocumentActivationCallbacks(this);
}
static const AtomicString& formatCodes()
{
DEFINE_STATIC_LOCAL(AtomicString, codes, ("AaNnXxMm"));
return codes;
}
bool WMLInputElement::isKeyboardFocusable(KeyboardEvent*) const
{
return WMLFormControlElement::isFocusable();
}
bool WMLInputElement::isMouseFocusable() const
{
return WMLFormControlElement::isFocusable();
}
void WMLInputElement::dispatchFocusEvent()
{
InputElement::dispatchFocusEvent(this, this);
WMLElement::dispatchFocusEvent();
}
void WMLInputElement::dispatchBlurEvent()
{
// Firstly check if it is allowed to leave this input field
String val = value();
if ((!m_isEmptyOk && val.isEmpty()) || !isConformedToInputMask(val)) {
updateFocusAppearance(true);
return;
}
// update the name variable of WML input elmenet
String nameVariable = formControlName();
if (!nameVariable.isEmpty())
wmlPageStateForDocument(document())->storeVariable(nameVariable, val);
InputElement::dispatchBlurEvent(this, this);
WMLElement::dispatchBlurEvent();
}
void WMLInputElement::updateFocusAppearance(bool restorePreviousSelection)
{
InputElement::updateFocusAppearance(m_data, this, this, restorePreviousSelection);
}
void WMLInputElement::aboutToUnload()
{
InputElement::aboutToUnload(this, this);
}
int WMLInputElement::size() const
{
return m_data.size();
}
const AtomicString& WMLInputElement::formControlType() const
{
// needs to be lowercase according to DOM spec
if (m_isPasswordField) {
DEFINE_STATIC_LOCAL(const AtomicString, password, ("password"));
return password;
}
DEFINE_STATIC_LOCAL(const AtomicString, text, ("text"));
return text;
}
const AtomicString& WMLInputElement::formControlName() const
{
return m_data.name();
}
const String& WMLInputElement::suggestedValue() const
{
return m_data.suggestedValue();
}
String WMLInputElement::value() const
{
String value = m_data.value();
if (value.isNull())
value = constrainValue(getAttribute(HTMLNames::valueAttr));
return value;
}
void WMLInputElement::setValue(const String& value, bool)
{
setFormControlValueMatchesRenderer(false);
m_data.setValue(constrainValue(value));
if (inDocument())
document()->updateStyleIfNeeded();
if (renderer())
renderer()->updateFromElement();
setNeedsStyleRecalc();
unsigned max = m_data.value().length();
if (document()->focusedNode() == this)
InputElement::updateSelectionRange(this, this, max, max);
else
cacheSelection(max, max);
InputElement::notifyFormStateChanged(this);
}
void WMLInputElement::setValueForUser(const String&)
{
/* InputElement class defines pure virtual function 'setValueForUser', which
will be useful only in HTMLInputElement. Do nothing in 'WMLInputElement'.
*/
}
void WMLInputElement::setValueFromRenderer(const String& value)
{
InputElement::setValueFromRenderer(m_data, this, this, value);
}
bool WMLInputElement::wasChangedSinceLastFormControlChangeEvent() const
{
return m_wasChangedSinceLastChangeEvent;
}
void WMLInputElement::setChangedSinceLastFormControlChangeEvent(bool changed)
{
m_wasChangedSinceLastChangeEvent = changed;
}
bool WMLInputElement::saveFormControlState(String& result) const
{
if (m_isPasswordField)
return false;
result = value();
return true;
}
void WMLInputElement::restoreFormControlState(const String& state)
{
ASSERT(!m_isPasswordField); // should never save/restore password fields
setValue(state);
}
void WMLInputElement::select()
{
if (RenderTextControl* r = toRenderTextControl(renderer()))
setSelectionRange(this, 0, r->text().length());
}
void WMLInputElement::accessKeyAction(bool)
{
// should never restore previous selection here
focus(false);
}
void WMLInputElement::parseMappedAttribute(Attribute* attr)
{
if (attr->name() == HTMLNames::nameAttr)
m_data.setName(parseValueForbiddingVariableReferences(attr->value()));
else if (attr->name() == HTMLNames::typeAttr) {
String type = parseValueForbiddingVariableReferences(attr->value());
m_isPasswordField = (type == "password");
} else if (attr->name() == HTMLNames::valueAttr) {
// We only need to setChanged if the form is looking at the default value right now.
if (m_data.value().isNull())
setNeedsStyleRecalc();
setFormControlValueMatchesRenderer(false);
} else if (attr->name() == HTMLNames::maxlengthAttr)
InputElement::parseMaxLengthAttribute(m_data, this, this, attr);
else if (attr->name() == HTMLNames::sizeAttr)
InputElement::parseSizeAttribute(m_data, this, attr);
else if (attr->name() == WMLNames::formatAttr)
m_formatMask = validateInputMask(parseValueForbiddingVariableReferences(attr->value()));
else if (attr->name() == WMLNames::emptyokAttr)
m_isEmptyOk = (attr->value() == "true");
else
WMLElement::parseMappedAttribute(attr);
// FIXME: Handle 'accesskey' attribute
// FIXME: Handle 'tabindex' attribute
// FIXME: Handle 'title' attribute
}
void WMLInputElement::copyNonAttributeProperties(const Element* source)
{
const WMLInputElement* sourceElement = static_cast<const WMLInputElement*>(source);
m_data.setValue(sourceElement->m_data.value());
WMLElement::copyNonAttributeProperties(source);
}
RenderObject* WMLInputElement::createRenderer(RenderArena* arena, RenderStyle*)
{
return new (arena) RenderTextControlSingleLine(this, false);
}
void WMLInputElement::detach()
{
WMLElement::detach();
setFormControlValueMatchesRenderer(false);
}
bool WMLInputElement::appendFormData(FormDataList& encoding, bool)
{
if (formControlName().isEmpty())
return false;
encoding.appendData(formControlName(), value());
return true;
}
void WMLInputElement::reset()
{
setValue(String());
}
void WMLInputElement::defaultEventHandler(Event* evt)
{
bool clickDefaultFormButton = false;
if (evt->type() == eventNames().textInputEvent && evt->isTextEvent()) {
TextEvent* textEvent = static_cast<TextEvent*>(evt);
if (textEvent->data() == "\n")
clickDefaultFormButton = true;
else if (renderer() && !isConformedToInputMask(textEvent->data()[0], toRenderTextControl(renderer())->text().length() + 1))
// If the inputed char doesn't conform to the input mask, stop handling
return;
}
if (evt->type() == eventNames().keydownEvent && evt->isKeyboardEvent() && focused() && document()->frame()
&& document()->frame()->editor()->doTextFieldCommandFromEvent(this, static_cast<KeyboardEvent*>(evt))) {
evt->setDefaultHandled();
return;
}
// Let the key handling done in EventTargetNode take precedence over the event handling here for editable text fields
if (!clickDefaultFormButton) {
WMLElement::defaultEventHandler(evt);
if (evt->defaultHandled())
return;
}
// Use key press event here since sending simulated mouse events
// on key down blocks the proper sending of the key press event.
if (evt->type() == eventNames().keypressEvent && evt->isKeyboardEvent()) {
// Simulate mouse click on the default form button for enter for these types of elements.
if (static_cast<KeyboardEvent*>(evt)->charCode() == '\r')
clickDefaultFormButton = true;
}
if (clickDefaultFormButton) {
// Fire onChange for text fields.
if (wasChangedSinceLastFormControlChangeEvent()) {
setChangedSinceLastFormControlChangeEvent(false);
dispatchEvent(Event::create(eventNames().changeEvent, true, false));
}
evt->setDefaultHandled();
return;
}
if (evt->isBeforeTextInsertedEvent())
InputElement::handleBeforeTextInsertedEvent(m_data, this, this, evt);
if (renderer() && (evt->isMouseEvent() || evt->isDragEvent() || evt->isWheelEvent() || evt->type() == eventNames().blurEvent || evt->type() == eventNames().focusEvent))
toRenderTextControlSingleLine(renderer())->forwardEvent(evt);
}
void WMLInputElement::cacheSelection(int start, int end)
{
m_data.setCachedSelectionStart(start);
m_data.setCachedSelectionEnd(end);
}
String WMLInputElement::constrainValue(const String& proposedValue) const
{
return InputElement::sanitizeUserInputValue(this, proposedValue, m_data.maxLength());
}
void WMLInputElement::documentDidBecomeActive()
{
ASSERT(m_isPasswordField);
reset();
}
void WMLInputElement::willMoveToNewOwnerDocument()
{
// Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered
if (m_isPasswordField)
document()->unregisterForDocumentActivationCallbacks(this);
WMLElement::willMoveToNewOwnerDocument();
}
void WMLInputElement::didMoveToNewOwnerDocument()
{
if (m_isPasswordField)
document()->registerForDocumentActivationCallbacks(this);
WMLElement::didMoveToNewOwnerDocument();
}
void WMLInputElement::initialize()
{
String nameVariable = formControlName();
String variableValue;
WMLPageState* pageSate = wmlPageStateForDocument(document());
ASSERT(pageSate);
if (!nameVariable.isEmpty())
variableValue = pageSate->getVariable(nameVariable);
if (variableValue.isEmpty() || !isConformedToInputMask(variableValue)) {
String val = value();
if (isConformedToInputMask(val))
variableValue = val;
else
variableValue = "";
pageSate->storeVariable(nameVariable, variableValue);
}
setValue(variableValue);
if (!hasAttribute(WMLNames::emptyokAttr)) {
if (m_formatMask.isEmpty() ||
// check if the format codes is just "*f"
(m_formatMask.length() == 2 && m_formatMask[0] == '*' && formatCodes().find(m_formatMask[1]) != notFound))
m_isEmptyOk = true;
}
}
String WMLInputElement::validateInputMask(const String& inputMask)
{
bool isValid = true;
bool hasWildcard = false;
unsigned escapeCharCount = 0;
unsigned maskLength = inputMask.length();
UChar formatCode;
for (unsigned i = 0; i < maskLength; ++i) {
formatCode = inputMask[i];
if (formatCodes().find(formatCode) == notFound) {
if (formatCode == '*' || (WTF::isASCIIDigit(formatCode) && formatCode != '0')) {
// validate codes which ends with '*f' or 'nf'
formatCode = inputMask[++i];
if ((i + 1 != maskLength) || formatCodes().find(formatCode) == notFound) {
isValid = false;
break;
}
hasWildcard = true;
} else if (formatCode == '\\') {
//skip over the next mask character
++i;
++escapeCharCount;
} else {
isValid = false;
break;
}
}
}
if (!isValid)
return String();
// calculate the number of characters allowed to be entered by input mask
m_numOfCharsAllowedByMask = maskLength;
if (escapeCharCount)
m_numOfCharsAllowedByMask -= escapeCharCount;
if (hasWildcard) {
formatCode = inputMask[maskLength - 2];
if (formatCode == '*')
m_numOfCharsAllowedByMask = m_data.maxLength();
else {
unsigned leftLen = String(&formatCode).toInt();
m_numOfCharsAllowedByMask = leftLen + m_numOfCharsAllowedByMask - 2;
}
}
return inputMask;
}
bool WMLInputElement::isConformedToInputMask(const String& inputChars)
{
for (unsigned i = 0; i < inputChars.length(); ++i)
if (!isConformedToInputMask(inputChars[i], i + 1, false))
return false;
return true;
}
bool WMLInputElement::isConformedToInputMask(UChar inChar, unsigned inputCharCount, bool isUserInput)
{
if (m_formatMask.isEmpty())
return true;
if (inputCharCount > m_numOfCharsAllowedByMask)
return false;
unsigned maskIndex = 0;
if (isUserInput) {
unsigned cursorPosition = 0;
if (renderer())
cursorPosition = toRenderTextControl(renderer())->selectionStart();
else
cursorPosition = m_data.cachedSelectionStart();
maskIndex = cursorPositionToMaskIndex(cursorPosition);
} else
maskIndex = cursorPositionToMaskIndex(inputCharCount - 1);
bool ok = true;
UChar mask = m_formatMask[maskIndex];
// match the inputed character with input mask
switch (mask) {
case 'A':
ok = !WTF::isASCIIDigit(inChar) && !WTF::isASCIILower(inChar) && WTF::isASCIIPrintable(inChar);
break;
case 'a':
ok = !WTF::isASCIIDigit(inChar) && !WTF::isASCIIUpper(inChar) && WTF::isASCIIPrintable(inChar);
break;
case 'N':
ok = WTF::isASCIIDigit(inChar);
break;
case 'n':
ok = !WTF::isASCIIAlpha(inChar) && WTF::isASCIIPrintable(inChar);
break;
case 'X':
ok = !WTF::isASCIILower(inChar) && WTF::isASCIIPrintable(inChar);
break;
case 'x':
ok = !WTF::isASCIIUpper(inChar) && WTF::isASCIIPrintable(inChar);
break;
case 'M':
ok = WTF::isASCIIPrintable(inChar);
break;
case 'm':
ok = WTF::isASCIIPrintable(inChar);
break;
default:
ok = (mask == inChar);
break;
}
return ok;
}
unsigned WMLInputElement::cursorPositionToMaskIndex(unsigned cursorPosition)
{
UChar mask;
int index = -1;
do {
mask = m_formatMask[++index];
if (mask == '\\')
++index;
else if (mask == '*' || (WTF::isASCIIDigit(mask) && mask != '0')) {
index = m_formatMask.length() - 1;
break;
}
} while (cursorPosition--);
return index;
}
}
#endif