| /* |
| * 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 "core/html/shadow/DateTimeEditElement.h" |
| |
| #include "bindings/core/v8/ExceptionStatePlaceholder.h" |
| #include "core/HTMLNames.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/StyleChangeReason.h" |
| #include "core/dom/Text.h" |
| #include "core/events/MouseEvent.h" |
| #include "core/html/forms/DateTimeFieldsState.h" |
| #include "core/html/shadow/DateTimeFieldElements.h" |
| #include "core/html/shadow/ShadowElementNames.h" |
| #include "core/style/ComputedStyle.h" |
| #include "core/style/StyleInheritedData.h" |
| #include "platform/fonts/FontCache.h" |
| #include "platform/text/DateTimeFormat.h" |
| #include "platform/text/PlatformLocale.h" |
| #include "wtf/DateMath.h" |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| using namespace WTF::Unicode; |
| |
| class DateTimeEditBuilder : private DateTimeFormat::TokenHandler { |
| 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 shouldAMPMFieldDisabled() const; |
| bool shouldDayOfMonthFieldDisabled() const; |
| bool shouldHourFieldDisabled() const; |
| bool shouldMillisecondFieldDisabled() const; |
| bool shouldMinuteFieldDisabled() const; |
| bool shouldSecondFieldDisabled() const; |
| bool shouldYearFieldDisabled() const; |
| inline const StepRange& stepRange() const { return m_parameters.stepRange; } |
| DateTimeNumericFieldElement::Step createStep(double msPerFieldUnit, |
| double msPerFieldSize) const; |
| |
| // DateTimeFormat::TokenHandler functions. |
| void visitField(DateTimeFormat::FieldType, int) final; |
| void visitLiteral(const String&) final; |
| |
| DateTimeEditElement& editElement() const; |
| |
| Member<DateTimeEditElement> m_editElement; |
| const DateComponents m_dateValue; |
| const DateTimeEditElement::LayoutParameters& m_parameters; |
| DateTimeNumericFieldElement::Range m_dayRange; |
| DateTimeNumericFieldElement::Range m_hour23Range; |
| DateTimeNumericFieldElement::Range m_minuteRange; |
| DateTimeNumericFieldElement::Range m_secondRange; |
| DateTimeNumericFieldElement::Range m_millisecondRange; |
| }; |
| |
| DateTimeEditBuilder::DateTimeEditBuilder( |
| DateTimeEditElement& element, |
| const DateTimeEditElement::LayoutParameters& layoutParameters, |
| const DateComponents& dateValue) |
| : m_editElement(&element), |
| m_dateValue(dateValue), |
| m_parameters(layoutParameters), |
| m_dayRange(1, 31), |
| m_hour23Range(0, 23), |
| m_minuteRange(0, 59), |
| m_secondRange(0, 59), |
| m_millisecondRange(0, 999) { |
| if (m_dateValue.getType() == DateComponents::Date || |
| m_dateValue.getType() == DateComponents::DateTimeLocal) { |
| if (m_parameters.minimum.getType() != DateComponents::Invalid && |
| m_parameters.maximum.getType() != DateComponents::Invalid && |
| m_parameters.minimum.fullYear() == m_parameters.maximum.fullYear() && |
| m_parameters.minimum.month() == m_parameters.maximum.month() && |
| m_parameters.minimum.monthDay() <= m_parameters.maximum.monthDay()) { |
| m_dayRange.minimum = m_parameters.minimum.monthDay(); |
| m_dayRange.maximum = m_parameters.maximum.monthDay(); |
| } |
| } |
| |
| if (m_dateValue.getType() == DateComponents::Time || |
| m_dayRange.isSingleton()) { |
| if (m_parameters.minimum.getType() != DateComponents::Invalid && |
| m_parameters.maximum.getType() != DateComponents::Invalid && |
| m_parameters.minimum.hour() <= m_parameters.maximum.hour()) { |
| m_hour23Range.minimum = m_parameters.minimum.hour(); |
| m_hour23Range.maximum = m_parameters.maximum.hour(); |
| } |
| } |
| |
| if (m_hour23Range.isSingleton() && |
| m_parameters.minimum.minute() <= m_parameters.maximum.minute()) { |
| m_minuteRange.minimum = m_parameters.minimum.minute(); |
| m_minuteRange.maximum = m_parameters.maximum.minute(); |
| } |
| if (m_minuteRange.isSingleton() && |
| m_parameters.minimum.second() <= m_parameters.maximum.second()) { |
| m_secondRange.minimum = m_parameters.minimum.second(); |
| m_secondRange.maximum = m_parameters.maximum.second(); |
| } |
| if (m_secondRange.isSingleton() && |
| m_parameters.minimum.millisecond() <= |
| m_parameters.maximum.millisecond()) { |
| m_millisecondRange.minimum = m_parameters.minimum.millisecond(); |
| m_millisecondRange.maximum = m_parameters.maximum.millisecond(); |
| } |
| } |
| |
| bool DateTimeEditBuilder::build(const String& formatString) { |
| 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& document = editElement().document(); |
| |
| switch (fieldType) { |
| case DateTimeFormat::FieldTypeDayOfMonth: { |
| DateTimeFieldElement* field = DateTimeDayFieldElement::create( |
| document, editElement(), m_parameters.placeholderForDay, m_dayRange); |
| editElement().addField(field); |
| if (shouldDayOfMonthFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeHour11: { |
| DateTimeNumericFieldElement::Step step = |
| createStep(msPerHour, msPerHour * 12); |
| DateTimeFieldElement* field = DateTimeHour11FieldElement::create( |
| document, editElement(), m_hour23Range, step); |
| editElement().addField(field); |
| if (shouldHourFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeHour12: { |
| DateTimeNumericFieldElement::Step step = |
| createStep(msPerHour, msPerHour * 12); |
| DateTimeFieldElement* field = DateTimeHour12FieldElement::create( |
| document, editElement(), m_hour23Range, step); |
| editElement().addField(field); |
| if (shouldHourFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeHour23: { |
| DateTimeNumericFieldElement::Step step = createStep(msPerHour, msPerDay); |
| DateTimeFieldElement* field = DateTimeHour23FieldElement::create( |
| document, editElement(), m_hour23Range, step); |
| editElement().addField(field); |
| if (shouldHourFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeHour24: { |
| DateTimeNumericFieldElement::Step step = createStep(msPerHour, msPerDay); |
| DateTimeFieldElement* field = DateTimeHour24FieldElement::create( |
| document, editElement(), m_hour23Range, step); |
| editElement().addField(field); |
| if (shouldHourFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeMinute: { |
| DateTimeNumericFieldElement::Step step = |
| createStep(msPerMinute, msPerHour); |
| DateTimeNumericFieldElement* field = DateTimeMinuteFieldElement::create( |
| document, editElement(), m_minuteRange, step); |
| editElement().addField(field); |
| if (shouldMinuteFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeMonth: // Fallthrough. |
| case DateTimeFormat::FieldTypeMonthStandAlone: { |
| int minMonth = 0, maxMonth = 11; |
| if (m_parameters.minimum.getType() != DateComponents::Invalid && |
| m_parameters.maximum.getType() != DateComponents::Invalid && |
| m_parameters.minimum.fullYear() == m_parameters.maximum.fullYear() && |
| m_parameters.minimum.month() <= m_parameters.maximum.month()) { |
| minMonth = m_parameters.minimum.month(); |
| maxMonth = m_parameters.maximum.month(); |
| } |
| DateTimeFieldElement* field; |
| switch (count) { |
| case countForNarrowMonth: // Fallthrough. |
| case countForAbbreviatedMonth: |
| field = DateTimeSymbolicMonthFieldElement::create( |
| document, editElement(), |
| fieldType == DateTimeFormat::FieldTypeMonth |
| ? m_parameters.locale.shortMonthLabels() |
| : m_parameters.locale.shortStandAloneMonthLabels(), |
| minMonth, maxMonth); |
| break; |
| case countForFullMonth: |
| field = DateTimeSymbolicMonthFieldElement::create( |
| document, editElement(), |
| fieldType == DateTimeFormat::FieldTypeMonth |
| ? m_parameters.locale.monthLabels() |
| : m_parameters.locale.standAloneMonthLabels(), |
| minMonth, maxMonth); |
| break; |
| default: |
| field = DateTimeMonthFieldElement::create( |
| document, editElement(), m_parameters.placeholderForMonth, |
| DateTimeNumericFieldElement::Range(minMonth + 1, maxMonth + 1)); |
| break; |
| } |
| editElement().addField(field); |
| if (minMonth == maxMonth && minMonth == m_dateValue.month() && |
| m_dateValue.getType() != DateComponents::Month) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypePeriod: { |
| DateTimeFieldElement* field = DateTimeAMPMFieldElement::create( |
| document, editElement(), m_parameters.locale.timeAMPMLabels()); |
| editElement().addField(field); |
| if (shouldAMPMFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeSecond: { |
| DateTimeNumericFieldElement::Step step = |
| createStep(msPerSecond, msPerMinute); |
| DateTimeNumericFieldElement* field = DateTimeSecondFieldElement::create( |
| document, editElement(), m_secondRange, step); |
| editElement().addField(field); |
| if (shouldSecondFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| |
| if (needMillisecondField()) { |
| visitLiteral(m_parameters.locale.localizedDecimalSeparator()); |
| visitField(DateTimeFormat::FieldTypeFractionalSecond, 3); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeFractionalSecond: { |
| DateTimeNumericFieldElement::Step step = createStep(1, msPerSecond); |
| DateTimeNumericFieldElement* field = |
| DateTimeMillisecondFieldElement::create(document, editElement(), |
| m_millisecondRange, step); |
| editElement().addField(field); |
| if (shouldMillisecondFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeWeekOfYear: { |
| DateTimeNumericFieldElement::Range range( |
| DateComponents::minimumWeekNumber, DateComponents::maximumWeekNumber); |
| if (m_parameters.minimum.getType() != DateComponents::Invalid && |
| m_parameters.maximum.getType() != DateComponents::Invalid && |
| m_parameters.minimum.fullYear() == m_parameters.maximum.fullYear() && |
| m_parameters.minimum.week() <= m_parameters.maximum.week()) { |
| range.minimum = m_parameters.minimum.week(); |
| range.maximum = m_parameters.maximum.week(); |
| } |
| editElement().addField( |
| DateTimeWeekFieldElement::create(document, editElement(), range)); |
| return; |
| } |
| |
| case DateTimeFormat::FieldTypeYear: { |
| DateTimeYearFieldElement::Parameters yearParams; |
| if (m_parameters.minimum.getType() == DateComponents::Invalid) { |
| yearParams.minimumYear = DateComponents::minimumYear(); |
| yearParams.minIsSpecified = false; |
| } else { |
| yearParams.minimumYear = m_parameters.minimum.fullYear(); |
| yearParams.minIsSpecified = true; |
| } |
| if (m_parameters.maximum.getType() == DateComponents::Invalid) { |
| yearParams.maximumYear = DateComponents::maximumYear(); |
| yearParams.maxIsSpecified = false; |
| } else { |
| yearParams.maximumYear = m_parameters.maximum.fullYear(); |
| 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; |
| DateTimeFieldElement* field = |
| DateTimeYearFieldElement::create(document, editElement(), yearParams); |
| editElement().addField(field); |
| if (shouldYearFieldDisabled()) { |
| field->setValueAsDate(m_dateValue); |
| field->setDisabled(); |
| } |
| return; |
| } |
| |
| default: |
| return; |
| } |
| } |
| |
| bool DateTimeEditBuilder::shouldAMPMFieldDisabled() const { |
| return shouldHourFieldDisabled() || |
| (m_hour23Range.minimum < 12 && m_hour23Range.maximum < 12 && |
| m_dateValue.hour() < 12) || |
| (m_hour23Range.minimum >= 12 && m_hour23Range.maximum >= 12 && |
| m_dateValue.hour() >= 12); |
| } |
| |
| bool DateTimeEditBuilder::shouldDayOfMonthFieldDisabled() const { |
| return m_dayRange.isSingleton() && |
| m_dayRange.minimum == m_dateValue.monthDay() && |
| m_dateValue.getType() != DateComponents::Date; |
| } |
| |
| bool DateTimeEditBuilder::shouldHourFieldDisabled() const { |
| if (m_hour23Range.isSingleton() && |
| m_hour23Range.minimum == m_dateValue.hour() && |
| !(shouldMinuteFieldDisabled() && shouldSecondFieldDisabled() && |
| shouldMillisecondFieldDisabled())) |
| return true; |
| |
| if (m_dateValue.getType() == DateComponents::Time) |
| return false; |
| DCHECK_EQ(m_dateValue.getType(), DateComponents::DateTimeLocal); |
| |
| if (shouldDayOfMonthFieldDisabled()) { |
| DCHECK_EQ(m_parameters.minimum.fullYear(), m_parameters.maximum.fullYear()); |
| DCHECK_EQ(m_parameters.minimum.month(), m_parameters.maximum.month()); |
| return false; |
| } |
| |
| const Decimal decimalMsPerDay(static_cast<int>(msPerDay)); |
| Decimal hourPartOfMinimum = |
| (stepRange().stepBase().abs().remainder(decimalMsPerDay) / |
| static_cast<int>(msPerHour)) |
| .floor(); |
| return hourPartOfMinimum == m_dateValue.hour() && |
| stepRange().step().remainder(decimalMsPerDay).isZero(); |
| } |
| |
| bool DateTimeEditBuilder::shouldMillisecondFieldDisabled() const { |
| if (m_millisecondRange.isSingleton() && |
| m_millisecondRange.minimum == m_dateValue.millisecond()) |
| return true; |
| |
| const Decimal decimalMsPerSecond(static_cast<int>(msPerSecond)); |
| return stepRange().stepBase().abs().remainder(decimalMsPerSecond) == |
| m_dateValue.millisecond() && |
| stepRange().step().remainder(decimalMsPerSecond).isZero(); |
| } |
| |
| bool DateTimeEditBuilder::shouldMinuteFieldDisabled() const { |
| if (m_minuteRange.isSingleton() && |
| m_minuteRange.minimum == m_dateValue.minute()) |
| return true; |
| |
| const Decimal decimalMsPerHour(static_cast<int>(msPerHour)); |
| Decimal minutePartOfMinimum = |
| (stepRange().stepBase().abs().remainder(decimalMsPerHour) / |
| static_cast<int>(msPerMinute)) |
| .floor(); |
| return minutePartOfMinimum == m_dateValue.minute() && |
| stepRange().step().remainder(decimalMsPerHour).isZero(); |
| } |
| |
| bool DateTimeEditBuilder::shouldSecondFieldDisabled() const { |
| if (m_secondRange.isSingleton() && |
| m_secondRange.minimum == m_dateValue.second()) |
| return true; |
| |
| const Decimal decimalMsPerMinute(static_cast<int>(msPerMinute)); |
| Decimal secondPartOfMinimum = |
| (stepRange().stepBase().abs().remainder(decimalMsPerMinute) / |
| static_cast<int>(msPerSecond)) |
| .floor(); |
| return secondPartOfMinimum == m_dateValue.second() && |
| stepRange().step().remainder(decimalMsPerMinute).isZero(); |
| } |
| |
| bool DateTimeEditBuilder::shouldYearFieldDisabled() const { |
| return m_parameters.minimum.getType() != DateComponents::Invalid && |
| m_parameters.maximum.getType() != DateComponents::Invalid && |
| m_parameters.minimum.fullYear() == m_parameters.maximum.fullYear() && |
| m_parameters.minimum.fullYear() == m_dateValue.fullYear(); |
| } |
| |
| void DateTimeEditBuilder::visitLiteral(const String& text) { |
| DEFINE_STATIC_LOCAL(AtomicString, textPseudoId, |
| ("-webkit-datetime-edit-text")); |
| DCHECK_GT(text.length(), 0u); |
| HTMLDivElement* element = HTMLDivElement::create(editElement().document()); |
| element->setShadowPseudoId(textPseudoId); |
| if (m_parameters.locale.isRTL() && text.length()) { |
| CharDirection dir = direction(text[0]); |
| if (dir == SegmentSeparator || dir == WhiteSpaceNeutral || |
| dir == OtherNeutral) |
| element->appendChild(Text::create(editElement().document(), |
| String(&rightToLeftMarkCharacter, 1))); |
| } |
| element->appendChild(Text::create(editElement().document(), text)); |
| editElement().fieldsWrapperElement()->appendChild(element); |
| } |
| |
| DateTimeEditElement& DateTimeEditBuilder::editElement() const { |
| return *m_editElement; |
| } |
| |
| DateTimeNumericFieldElement::Step DateTimeEditBuilder::createStep( |
| double msPerFieldUnit, |
| double msPerFieldSize) const { |
| const Decimal msPerFieldUnitDecimal(static_cast<int>(msPerFieldUnit)); |
| const Decimal msPerFieldSizeDecimal(static_cast<int>(msPerFieldSize)); |
| Decimal stepMilliseconds = stepRange().step(); |
| DCHECK(!msPerFieldUnitDecimal.isZero()); |
| DCHECK(!msPerFieldSizeDecimal.isZero()); |
| DCHECK(!stepMilliseconds.isZero()); |
| |
| DateTimeNumericFieldElement::Step step(1, 0); |
| |
| if (stepMilliseconds.remainder(msPerFieldSizeDecimal).isZero()) |
| stepMilliseconds = msPerFieldSizeDecimal; |
| |
| if (msPerFieldSizeDecimal.remainder(stepMilliseconds).isZero() && |
| stepMilliseconds.remainder(msPerFieldUnitDecimal).isZero()) { |
| step.step = |
| static_cast<int>((stepMilliseconds / msPerFieldUnitDecimal).toDouble()); |
| step.stepBase = static_cast<int>( |
| (stepRange().stepBase() / msPerFieldUnitDecimal) |
| .floor() |
| .remainder(msPerFieldSizeDecimal / msPerFieldUnitDecimal) |
| .toDouble()); |
| } |
| return step; |
| } |
| |
| // ---------------------------- |
| |
| DateTimeEditElement::EditControlOwner::~EditControlOwner() {} |
| |
| DateTimeEditElement::DateTimeEditElement(Document& document, |
| EditControlOwner& editControlOwner) |
| : HTMLDivElement(document), m_editControlOwner(&editControlOwner) { |
| setHasCustomStyleCallbacks(); |
| } |
| |
| DateTimeEditElement::~DateTimeEditElement() {} |
| |
| DEFINE_TRACE(DateTimeEditElement) { |
| visitor->trace(m_fields); |
| visitor->trace(m_editControlOwner); |
| HTMLDivElement::trace(visitor); |
| } |
| |
| inline Element* DateTimeEditElement::fieldsWrapperElement() const { |
| DCHECK(firstChild()); |
| return toElement(firstChild()); |
| } |
| |
| void DateTimeEditElement::addField(DateTimeFieldElement* field) { |
| if (m_fields.size() >= maximumNumberOfFields) |
| return; |
| m_fields.append(field); |
| fieldsWrapperElement()->appendChild(field); |
| } |
| |
| bool DateTimeEditElement::anyEditableFieldsHaveValues() const { |
| for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex) { |
| if (!m_fields[fieldIndex]->isDisabled() && m_fields[fieldIndex]->hasValue()) |
| return true; |
| } |
| return false; |
| } |
| |
| void DateTimeEditElement::blurByOwner() { |
| if (DateTimeFieldElement* field = focusedField()) |
| field->blur(); |
| } |
| |
| DateTimeEditElement* DateTimeEditElement::create( |
| Document& document, |
| EditControlOwner& editControlOwner) { |
| DateTimeEditElement* container = |
| new DateTimeEditElement(document, editControlOwner); |
| container->setShadowPseudoId(AtomicString("-webkit-datetime-edit")); |
| container->setAttribute(idAttr, ShadowElementNames::dateTimeEdit()); |
| return container; |
| } |
| |
| PassRefPtr<ComputedStyle> DateTimeEditElement::customStyleForLayoutObject() { |
| // FIXME: This is a kind of layout. We might want to introduce new |
| // layoutObject. |
| RefPtr<ComputedStyle> originalStyle = originalStyleForLayoutObject(); |
| RefPtr<ComputedStyle> style = ComputedStyle::clone(*originalStyle); |
| float width = 0; |
| for (Node* child = fieldsWrapperElement()->firstChild(); child; |
| child = child->nextSibling()) { |
| if (!child->isElementNode()) |
| continue; |
| Element* childElement = toElement(child); |
| if (childElement->isDateTimeFieldElement()) { |
| // We need to pass the ComputedStyle of this element because child |
| // elements can't resolve inherited style at this timing. |
| width += static_cast<DateTimeFieldElement*>(childElement) |
| ->maximumWidth(*style); |
| } else { |
| // ::-webkit-datetime-edit-text case. It has no |
| // border/padding/margin in html.css. |
| width += DateTimeFieldElement::computeTextWidth( |
| *style, childElement->textContent()); |
| } |
| } |
| style->setWidth(Length(ceilf(width), Fixed)); |
| style->setUnique(); |
| return style.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].get() : 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; |
| focusOnNextFocusableField(0); |
| } |
| |
| void DateTimeEditElement::focusByOwner(Element* oldFocusedElement) { |
| if (oldFocusedElement && oldFocusedElement->isDateTimeFieldElement()) { |
| DateTimeFieldElement* oldFocusedField = |
| static_cast<DateTimeFieldElement*>(oldFocusedElement); |
| size_t index = fieldIndexOf(*oldFocusedField); |
| document().updateStyleAndLayoutTreeForNode(oldFocusedField); |
| if (index != invalidFieldIndex && oldFocusedField->isFocusable()) { |
| oldFocusedField->focus(); |
| return; |
| } |
| } |
| focusOnNextFocusableField(0); |
| } |
| |
| DateTimeFieldElement* DateTimeEditElement::focusedField() const { |
| return fieldAt(focusedFieldIndex()); |
| } |
| |
| size_t DateTimeEditElement::focusedFieldIndex() const { |
| Element* const focusedFieldElement = document().focusedElement(); |
| for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex) { |
| if (m_fields[fieldIndex] == focusedFieldElement) |
| return fieldIndex; |
| } |
| return invalidFieldIndex; |
| } |
| |
| void DateTimeEditElement::fieldValueChanged() { |
| if (m_editControlOwner) |
| m_editControlOwner->editControlValueChanged(); |
| } |
| |
| bool DateTimeEditElement::focusOnNextFocusableField(size_t startIndex) { |
| document().updateStyleAndLayoutTreeIgnorePendingStylesheets(); |
| for (size_t fieldIndex = startIndex; fieldIndex < m_fields.size(); |
| ++fieldIndex) { |
| if (m_fields[fieldIndex]->isFocusable()) { |
| m_fields[fieldIndex]->focus(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool DateTimeEditElement::focusOnNextField(const DateTimeFieldElement& field) { |
| const size_t startFieldIndex = fieldIndexOf(field); |
| if (startFieldIndex == invalidFieldIndex) |
| return false; |
| return focusOnNextFocusableField(startFieldIndex + 1); |
| } |
| |
| bool DateTimeEditElement::focusOnPreviousField( |
| const DateTimeFieldElement& field) { |
| const size_t startFieldIndex = fieldIndexOf(field); |
| if (startFieldIndex == invalidFieldIndex) |
| return false; |
| document().updateStyleAndLayoutTreeIgnorePendingStylesheets(); |
| size_t fieldIndex = startFieldIndex; |
| while (fieldIndex > 0) { |
| --fieldIndex; |
| if (m_fields[fieldIndex]->isFocusable()) { |
| m_fields[fieldIndex]->focus(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool DateTimeEditElement::isDateTimeEditElement() const { |
| return true; |
| } |
| |
| bool DateTimeEditElement::isDisabled() const { |
| return m_editControlOwner && m_editControlOwner->isEditControlOwnerDisabled(); |
| } |
| |
| bool DateTimeEditElement::isFieldOwnerDisabled() const { |
| return isDisabled(); |
| } |
| |
| bool DateTimeEditElement::isFieldOwnerReadOnly() const { |
| return isReadOnly(); |
| } |
| |
| bool DateTimeEditElement::isReadOnly() const { |
| return m_editControlOwner && m_editControlOwner->isEditControlOwnerReadOnly(); |
| } |
| |
| void DateTimeEditElement::layout(const LayoutParameters& layoutParameters, |
| const DateComponents& dateValue) { |
| DEFINE_STATIC_LOCAL(AtomicString, fieldsWrapperPseudoId, |
| ("-webkit-datetime-edit-fields-wrapper")); |
| if (!hasChildren()) { |
| HTMLDivElement* element = HTMLDivElement::create(document()); |
| element->setShadowPseudoId(fieldsWrapperPseudoId); |
| appendChild(element); |
| } |
| Element* fieldsWrapper = fieldsWrapperElement(); |
| |
| size_t focusedFieldIndex = this->focusedFieldIndex(); |
| DateTimeFieldElement* const focusedField = fieldAt(focusedFieldIndex); |
| const AtomicString focusedFieldId = |
| focusedField ? focusedField->shadowPseudoId() : nullAtom; |
| |
| DateTimeEditBuilder builder(*this, layoutParameters, dateValue); |
| Node* lastChildToBeRemoved = fieldsWrapper->lastChild(); |
| if (!builder.build(layoutParameters.dateTimeFormat) || m_fields.isEmpty()) { |
| lastChildToBeRemoved = fieldsWrapper->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 = fieldsWrapper->firstChild(); childNode; |
| childNode = fieldsWrapper->firstChild()) { |
| fieldsWrapper->removeChild(childNode); |
| if (childNode == lastChildToBeRemoved) |
| break; |
| } |
| setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create( |
| StyleChangeReason::Control)); |
| } |
| } |
| |
| AtomicString DateTimeEditElement::localeIdentifier() const { |
| return m_editControlOwner ? m_editControlOwner->localeIdentifier() : nullAtom; |
| } |
| |
| void DateTimeEditElement::fieldDidChangeValueByKeyboard() { |
| if (m_editControlOwner) |
| m_editControlOwner->editControlDidChangeValueByKeyboard(); |
| } |
| |
| 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) { |
| for (size_t fieldIndex = 0; fieldIndex < m_fields.size(); ++fieldIndex) |
| m_fields[fieldIndex]->setValueAsDateTimeFieldsState(dateTimeFieldsState); |
| } |
| |
| 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(DateTimeFieldElement::DispatchNoEvent); |
| } |
| |
| bool DateTimeEditElement::hasFocusedField() { |
| return focusedFieldIndex() != invalidFieldIndex; |
| } |
| |
| void DateTimeEditElement::setOnlyYearMonthDay(const DateComponents& date) { |
| DCHECK_EQ(date.getType(), DateComponents::Date); |
| |
| if (!m_editControlOwner) |
| return; |
| |
| DateTimeFieldsState dateTimeFieldsState = valueAsDateTimeFieldsState(); |
| dateTimeFieldsState.setYear(date.fullYear()); |
| dateTimeFieldsState.setMonth(date.month() + 1); |
| dateTimeFieldsState.setDayOfMonth(date.monthDay()); |
| setValueAsDateTimeFieldsState(dateTimeFieldsState); |
| m_editControlOwner->editControlValueChanged(); |
| } |
| |
| 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()) { |
| 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 blink |