blob: dc6b257610c0a834905fc6a849dfee77f418d62c [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "config.h"
#if ENABLE(INPUT_MULTIPLE_FIELDS_UI)
#include "DateTimeEditElement.h"
#include "DateComponents.h"
#include "DateTimeFieldElements.h"
#include "DateTimeFieldsState.h"
#include "DateTimeFormat.h"
#include "DateTimeSymbolicFieldElement.h"
#include "EventHandler.h"
#include "HTMLNames.h"
#include "KeyboardEvent.h"
#include "MouseEvent.h"
#include "PlatformLocale.h"
#include "Text.h"
#include <wtf/DateMath.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore {
using namespace HTMLNames;
class DateTimeEditBuilder : private DateTimeFormat::TokenHandler {
WTF_MAKE_NONCOPYABLE(DateTimeEditBuilder);
public:
// The argument objects must be alive until this object dies.
DateTimeEditBuilder(DateTimeEditElement&, const DateTimeEditElement::LayoutParameters&, const DateComponents&);
bool build(const String&);
private:
bool needMillisecondField() const;
bool shouldMillisecondFieldReadOnly() const;
bool shouldMinuteFieldReadOnly() const;
bool shouldSecondFieldReadOnly() const;
inline const StepRange& stepRange() const { return m_parameters.stepRange; }
// DateTimeFormat::TokenHandler functions.
virtual void visitField(DateTimeFormat::FieldType, int) OVERRIDE FINAL;
virtual void visitLiteral(const String&) OVERRIDE FINAL;
DateTimeEditElement& m_editElement;
const DateComponents m_dateValue;
const DateTimeEditElement::LayoutParameters& m_parameters;
};
DateTimeEditBuilder::DateTimeEditBuilder(DateTimeEditElement& elemnt, const DateTimeEditElement::LayoutParameters& layoutParameters, const DateComponents& dateValue)
: m_editElement(elemnt)
, m_dateValue(dateValue)
, m_parameters(layoutParameters)
{
}
bool DateTimeEditBuilder::build(const String& formatString)
{
m_editElement.resetFields();
return DateTimeFormat::parse(formatString, *this);
}
bool DateTimeEditBuilder::needMillisecondField() const
{
return m_dateValue.millisecond()
|| !stepRange().minimum().remainder(static_cast<int>(msPerSecond)).isZero()
|| !stepRange().step().remainder(static_cast<int>(msPerSecond)).isZero();
}
void DateTimeEditBuilder::visitField(DateTimeFormat::FieldType fieldType, int count)
{
const int countForAbbreviatedMonth = 3;
const int countForFullMonth = 4;
const int countForNarrowMonth = 5;
Document* const document = m_editElement.document();
switch (fieldType) {
case DateTimeFormat::FieldTypeDayOfMonth:
m_editElement.addField(DateTimeDayFieldElement::create(document, m_editElement, m_parameters.placeholderForDay));
return;
case DateTimeFormat::FieldTypeHour11:
m_editElement.addField(DateTimeHourFieldElement::create(document, m_editElement, 0, 11));
return;
case DateTimeFormat::FieldTypeHour12:
m_editElement.addField(DateTimeHourFieldElement::create(document, m_editElement, 1, 12));
return;
case DateTimeFormat::FieldTypeHour23:
m_editElement.addField(DateTimeHourFieldElement::create(document, m_editElement, 0, 23));
return;
case DateTimeFormat::FieldTypeHour24:
m_editElement.addField(DateTimeHourFieldElement::create(document, m_editElement, 1, 24));
return;
case DateTimeFormat::FieldTypeMinute: {
RefPtr<DateTimeNumericFieldElement> field = DateTimeMinuteFieldElement::create(document, m_editElement);
m_editElement.addField(field);
if (shouldMinuteFieldReadOnly())
field->setReadOnly();
return;
}
case DateTimeFormat::FieldTypeMonth:
switch (count) {
case countForNarrowMonth: // Fallthrough.
case countForAbbreviatedMonth:
m_editElement.addField(DateTimeSymbolicMonthFieldElement::create(document, m_editElement, m_parameters.locale.shortMonthLabels()));
break;
case countForFullMonth:
m_editElement.addField(DateTimeSymbolicMonthFieldElement::create(document, m_editElement, m_parameters.locale.monthLabels()));
break;
default:
m_editElement.addField(DateTimeMonthFieldElement::create(document, m_editElement, m_parameters.placeholderForMonth));
break;
}
return;
case DateTimeFormat::FieldTypeMonthStandAlone:
switch (count) {
case countForNarrowMonth: // Fallthrough.
case countForAbbreviatedMonth:
m_editElement.addField(DateTimeSymbolicMonthFieldElement::create(document, m_editElement, m_parameters.locale.shortStandAloneMonthLabels()));
break;
case countForFullMonth:
m_editElement.addField(DateTimeSymbolicMonthFieldElement::create(document, m_editElement, m_parameters.locale.standAloneMonthLabels()));
break;
default:
m_editElement.addField(DateTimeMonthFieldElement::create(document, m_editElement, m_parameters.placeholderForMonth));
break;
}
return;
case DateTimeFormat::FieldTypePeriod:
m_editElement.addField(DateTimeAMPMFieldElement::create(document, m_editElement, m_parameters.locale.timeAMPMLabels()));
return;
case DateTimeFormat::FieldTypeSecond: {
RefPtr<DateTimeSecondFieldElement> field = DateTimeSecondFieldElement::create(document, m_editElement);
m_editElement.addField(field);
if (shouldSecondFieldReadOnly())
field->setReadOnly();
if (needMillisecondField()) {
visitLiteral(m_parameters.locale.localizedDecimalSeparator());
visitField(DateTimeFormat::FieldTypeFractionalSecond, 3);
}
return;
}
case DateTimeFormat::FieldTypeFractionalSecond: {
RefPtr<DateTimeMillisecondFieldElement> field = DateTimeMillisecondFieldElement::create(document, m_editElement);
m_editElement.addField(field);
if (shouldMillisecondFieldReadOnly())
field->setReadOnly();
return;
}
case DateTimeFormat::FieldTypeWeekOfYear:
m_editElement.addField(DateTimeWeekFieldElement::create(document, m_editElement));
return;
case DateTimeFormat::FieldTypeYear: {
DateTimeYearFieldElement::Parameters yearParams;
if (m_parameters.minimumYear == m_parameters.undefinedYear()) {
yearParams.minimumYear = DateComponents::minimumYear();
yearParams.minIsSpecified = false;
} else {
yearParams.minimumYear = m_parameters.minimumYear;
yearParams.minIsSpecified = true;
}
if (m_parameters.maximumYear == m_parameters.undefinedYear()) {
yearParams.maximumYear = DateComponents::maximumYear();
yearParams.maxIsSpecified = false;
} else {
yearParams.maximumYear = m_parameters.maximumYear;
yearParams.maxIsSpecified = true;
}
if (yearParams.minimumYear > yearParams.maximumYear) {
std::swap(yearParams.minimumYear, yearParams.maximumYear);
std::swap(yearParams.minIsSpecified, yearParams.maxIsSpecified);
}
yearParams.placeholder = m_parameters.placeholderForYear;
m_editElement.addField(DateTimeYearFieldElement::create(document, m_editElement, yearParams));
return;
}
default:
return;
}
}
bool DateTimeEditBuilder::shouldMillisecondFieldReadOnly() const
{
return !m_dateValue.millisecond() && stepRange().step().remainder(static_cast<int>(msPerSecond)).isZero();
}
bool DateTimeEditBuilder::shouldMinuteFieldReadOnly() const
{
return !m_dateValue.minute() && stepRange().step().remainder(static_cast<int>(msPerHour)).isZero();
}
bool DateTimeEditBuilder::shouldSecondFieldReadOnly() const
{
return !m_dateValue.second() && stepRange().step().remainder(static_cast<int>(msPerMinute)).isZero();
}
void DateTimeEditBuilder::visitLiteral(const String& text)
{
DEFINE_STATIC_LOCAL(AtomicString, textPseudoId, ("-webkit-datetime-edit-text", AtomicString::ConstructFromLiteral));
ASSERT(text.length());
RefPtr<HTMLDivElement> element = HTMLDivElement::create(m_editElement.document());
element->setPseudo(textPseudoId);
element->appendChild(Text::create(m_editElement.document(), text));
m_editElement.appendChild(element);
}
// ----------------------------
DateTimeEditElement::EditControlOwner::~EditControlOwner()
{
}
DateTimeEditElement::DateTimeEditElement(Document* document, EditControlOwner& editControlOwner)
: HTMLDivElement(divTag, document)
, m_editControlOwner(&editControlOwner)
{
DEFINE_STATIC_LOCAL(AtomicString, dateTimeEditPseudoId, ("-webkit-datetime-edit", AtomicString::ConstructFromLiteral));
setPseudo(dateTimeEditPseudoId);
}
DateTimeEditElement::~DateTimeEditElement()
{
for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex)
m_fields[fieldIndex]->removeEventHandler();
}
void DateTimeEditElement::addField(PassRefPtr<DateTimeFieldElement> field)
{
if (m_fields.size() == m_fields.capacity())
return;
m_fields.append(field.get());
appendChild(field);
}
void DateTimeEditElement::blurByOwner()
{
if (DateTimeFieldElement* field = focusedField())
field->blur();
}
PassRefPtr<DateTimeEditElement> DateTimeEditElement::create(Document* document, EditControlOwner& editControlOwner)
{
RefPtr<DateTimeEditElement> container = adoptRef(new DateTimeEditElement(document, editControlOwner));
return container.release();
}
void DateTimeEditElement::didBlurFromField()
{
if (m_editControlOwner)
m_editControlOwner->didBlurFromControl();
}
void DateTimeEditElement::didFocusOnField()
{
if (m_editControlOwner)
m_editControlOwner->didFocusOnControl();
}
void DateTimeEditElement::disabledStateChanged()
{
updateUIState();
}
DateTimeFieldElement* DateTimeEditElement::fieldAt(size_t fieldIndex) const
{
return fieldIndex < m_fields.size() ? m_fields[fieldIndex] : 0;
}
size_t DateTimeEditElement::fieldIndexOf(const DateTimeFieldElement& field) const
{
for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex) {
if (m_fields[fieldIndex] == &field)
return fieldIndex;
}
return invalidFieldIndex;
}
void DateTimeEditElement::focusIfNoFocus()
{
if (focusedFieldIndex() != invalidFieldIndex)
return;
if (DateTimeFieldElement* field = fieldAt(0))
field->focus();
}
void DateTimeEditElement::focusByOwner()
{
if (DateTimeFieldElement* field = fieldAt(0))
field->focus();
}
DateTimeFieldElement* DateTimeEditElement::focusedField() const
{
return fieldAt(focusedFieldIndex());
}
size_t DateTimeEditElement::focusedFieldIndex() const
{
Node* const focusedFieldNode = document()->focusedNode();
for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex) {
if (m_fields[fieldIndex] == focusedFieldNode)
return fieldIndex;
}
return invalidFieldIndex;
}
void DateTimeEditElement::fieldValueChanged()
{
if (m_editControlOwner)
m_editControlOwner->editControlValueChanged();
}
bool DateTimeEditElement::focusOnNextField(const DateTimeFieldElement& field)
{
const size_t startFieldIndex = fieldIndexOf(field);
if (startFieldIndex == invalidFieldIndex)
return false;
for (size_t fieldIndex = startFieldIndex + 1; fieldIndex < m_fields.size(); ++fieldIndex) {
if (!m_fields[fieldIndex]->isReadOnly()) {
m_fields[fieldIndex]->focus();
return true;
}
}
return false;
}
bool DateTimeEditElement::focusOnPreviousField(const DateTimeFieldElement& field)
{
const size_t startFieldIndex = fieldIndexOf(field);
if (startFieldIndex == invalidFieldIndex)
return false;
size_t fieldIndex = startFieldIndex;
while (fieldIndex > 0) {
--fieldIndex;
if (!m_fields[fieldIndex]->isReadOnly()) {
m_fields[fieldIndex]->focus();
return true;
}
}
return false;
}
bool DateTimeEditElement::isDisabled() const
{
return m_editControlOwner && m_editControlOwner->isEditControlOwnerDisabled();
}
bool DateTimeEditElement::isFieldOwnerDisabledOrReadOnly() const
{
return isDisabled() || isReadOnly();
}
bool DateTimeEditElement::isReadOnly() const
{
return m_editControlOwner && m_editControlOwner->isEditControlOwnerReadOnly();
}
void DateTimeEditElement::layout(const LayoutParameters& layoutParameters, const DateComponents& dateValue)
{
size_t focusedFieldIndex = this->focusedFieldIndex();
DateTimeFieldElement* const focusedField = fieldAt(focusedFieldIndex);
const AtomicString focusedFieldId = focusedField ? focusedField->shadowPseudoId() : nullAtom;
DateTimeEditBuilder builder(*this, layoutParameters, dateValue);
Node* lastChildToBeRemoved = lastChild();
if (!builder.build(layoutParameters.dateTimeFormat) || m_fields.isEmpty()) {
lastChildToBeRemoved = lastChild();
builder.build(layoutParameters.fallbackDateTimeFormat);
}
if (focusedFieldIndex != invalidFieldIndex) {
for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex) {
if (m_fields[fieldIndex]->shadowPseudoId() == focusedFieldId) {
focusedFieldIndex = fieldIndex;
break;
}
}
if (DateTimeFieldElement* field = fieldAt(std::min(focusedFieldIndex, m_fields.size() - 1)))
field->focus();
}
if (lastChildToBeRemoved) {
for (Node* childNode = firstChild(); childNode; childNode = firstChild()) {
removeChild(childNode);
if (childNode == lastChildToBeRemoved)
break;
}
}
}
AtomicString DateTimeEditElement::localeIdentifier() const
{
return m_editControlOwner ? m_editControlOwner->localeIdentifier() : nullAtom;
}
void DateTimeEditElement::readOnlyStateChanged()
{
updateUIState();
}
void DateTimeEditElement::resetFields()
{
for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex)
m_fields[fieldIndex]->removeEventHandler();
m_fields.shrink(0);
}
void DateTimeEditElement::defaultEventHandler(Event* event)
{
// In case of control owner forward event to control, e.g. DOM
// dispatchEvent method.
if (DateTimeFieldElement* field = focusedField()) {
field->defaultEventHandler(event);
if (event->defaultHandled())
return;
}
HTMLDivElement::defaultEventHandler(event);
}
void DateTimeEditElement::setValueAsDate(const LayoutParameters& layoutParameters, const DateComponents& date)
{
layout(layoutParameters, date);
for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex)
m_fields[fieldIndex]->setValueAsDate(date);
}
void DateTimeEditElement::setValueAsDateTimeFieldsState(const DateTimeFieldsState& dateTimeFieldsState, const DateComponents& dateForReadOnlyField)
{
for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex)
m_fields[fieldIndex]->setValueAsDateTimeFieldsState(dateTimeFieldsState, dateForReadOnlyField);
}
void DateTimeEditElement::setEmptyValue(const LayoutParameters& layoutParameters, const DateComponents& dateForReadOnlyField)
{
layout(layoutParameters, dateForReadOnlyField);
for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex)
m_fields[fieldIndex]->setEmptyValue(dateForReadOnlyField, DateTimeFieldElement::DispatchNoEvent);
}
bool DateTimeEditElement::hasFocusedField()
{
return focusedFieldIndex() != invalidFieldIndex;
}
void DateTimeEditElement::stepDown()
{
if (DateTimeFieldElement* const field = focusedField())
field->stepDown();
}
void DateTimeEditElement::stepUp()
{
if (DateTimeFieldElement* const field = focusedField())
field->stepUp();
}
void DateTimeEditElement::updateUIState()
{
if (isDisabled() || isReadOnly()) {
if (DateTimeFieldElement* field = focusedField())
field->blur();
}
}
String DateTimeEditElement::value() const
{
if (!m_editControlOwner)
return emptyString();
return m_editControlOwner->formatDateTimeFieldsState(valueAsDateTimeFieldsState());
}
DateTimeFieldsState DateTimeEditElement::valueAsDateTimeFieldsState() const
{
DateTimeFieldsState dateTimeFieldsState;
for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex)
m_fields[fieldIndex]->populateDateTimeFieldsState(dateTimeFieldsState);
return dateTimeFieldsState;
}
} // namespace WebCore
#endif