| /* |
| * 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/forms/DateTimeEditElement.h" |
| |
| #include "bindings/core/v8/ExceptionState.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/DateTimeFieldElements.h" |
| #include "core/html/forms/DateTimeFieldsState.h" |
| #include "core/html/shadow/ShadowElementNames.h" |
| #include "core/style/ComputedStyle.h" |
| #include "platform/fonts/FontCache.h" |
| #include "platform/text/DateTimeFormat.h" |
| #include "platform/text/PlatformLocale.h" |
| #include "platform/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& GetStepRange() const { |
| return parameters_.step_range; |
| } |
| DateTimeNumericFieldElement::Step CreateStep(double ms_per_field_unit, |
| double ms_per_field_size) const; |
| |
| // DateTimeFormat::TokenHandler functions. |
| void VisitField(DateTimeFormat::FieldType, int) final; |
| void VisitLiteral(const String&) final; |
| |
| DateTimeEditElement& EditElement() const; |
| |
| Member<DateTimeEditElement> edit_element_; |
| const DateComponents date_value_; |
| const DateTimeEditElement::LayoutParameters& parameters_; |
| DateTimeNumericFieldElement::Range day_range_; |
| DateTimeNumericFieldElement::Range hour23_range_; |
| DateTimeNumericFieldElement::Range minute_range_; |
| DateTimeNumericFieldElement::Range second_range_; |
| DateTimeNumericFieldElement::Range millisecond_range_; |
| }; |
| |
| DateTimeEditBuilder::DateTimeEditBuilder( |
| DateTimeEditElement& element, |
| const DateTimeEditElement::LayoutParameters& layout_parameters, |
| const DateComponents& date_value) |
| : edit_element_(&element), |
| date_value_(date_value), |
| parameters_(layout_parameters), |
| day_range_(1, 31), |
| hour23_range_(0, 23), |
| minute_range_(0, 59), |
| second_range_(0, 59), |
| millisecond_range_(0, 999) { |
| if (date_value_.GetType() == DateComponents::kDate || |
| date_value_.GetType() == DateComponents::kDateTimeLocal) { |
| if (parameters_.minimum.GetType() != DateComponents::kInvalid && |
| parameters_.maximum.GetType() != DateComponents::kInvalid && |
| parameters_.minimum.FullYear() == parameters_.maximum.FullYear() && |
| parameters_.minimum.Month() == parameters_.maximum.Month() && |
| parameters_.minimum.MonthDay() <= parameters_.maximum.MonthDay()) { |
| day_range_.minimum = parameters_.minimum.MonthDay(); |
| day_range_.maximum = parameters_.maximum.MonthDay(); |
| } |
| } |
| |
| if (date_value_.GetType() == DateComponents::kTime || |
| day_range_.IsSingleton()) { |
| if (parameters_.minimum.GetType() != DateComponents::kInvalid && |
| parameters_.maximum.GetType() != DateComponents::kInvalid && |
| parameters_.minimum.Hour() <= parameters_.maximum.Hour()) { |
| hour23_range_.minimum = parameters_.minimum.Hour(); |
| hour23_range_.maximum = parameters_.maximum.Hour(); |
| } |
| } |
| |
| if (hour23_range_.IsSingleton() && |
| parameters_.minimum.Minute() <= parameters_.maximum.Minute()) { |
| minute_range_.minimum = parameters_.minimum.Minute(); |
| minute_range_.maximum = parameters_.maximum.Minute(); |
| } |
| if (minute_range_.IsSingleton() && |
| parameters_.minimum.Second() <= parameters_.maximum.Second()) { |
| second_range_.minimum = parameters_.minimum.Second(); |
| second_range_.maximum = parameters_.maximum.Second(); |
| } |
| if (second_range_.IsSingleton() && |
| parameters_.minimum.Millisecond() <= parameters_.maximum.Millisecond()) { |
| millisecond_range_.minimum = parameters_.minimum.Millisecond(); |
| millisecond_range_.maximum = parameters_.maximum.Millisecond(); |
| } |
| } |
| |
| bool DateTimeEditBuilder::Build(const String& format_string) { |
| EditElement().ResetFields(); |
| return DateTimeFormat::Parse(format_string, *this); |
| } |
| |
| bool DateTimeEditBuilder::NeedMillisecondField() const { |
| return date_value_.Millisecond() || |
| !GetStepRange() |
| .Minimum() |
| .Remainder(static_cast<int>(kMsPerSecond)) |
| .IsZero() || |
| !GetStepRange() |
| .Step() |
| .Remainder(static_cast<int>(kMsPerSecond)) |
| .IsZero(); |
| } |
| |
| void DateTimeEditBuilder::VisitField(DateTimeFormat::FieldType field_type, |
| int count) { |
| const int kCountForAbbreviatedMonth = 3; |
| const int kCountForFullMonth = 4; |
| const int kCountForNarrowMonth = 5; |
| Document& document = EditElement().GetDocument(); |
| |
| switch (field_type) { |
| case DateTimeFormat::kFieldTypeDayOfMonth: { |
| DateTimeFieldElement* field = DateTimeDayFieldElement::Create( |
| document, EditElement(), parameters_.placeholder_for_day, day_range_); |
| EditElement().AddField(field); |
| if (ShouldDayOfMonthFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeHour11: { |
| DateTimeNumericFieldElement::Step step = |
| CreateStep(kMsPerHour, kMsPerHour * 12); |
| DateTimeFieldElement* field = DateTimeHour11FieldElement::Create( |
| document, EditElement(), hour23_range_, step); |
| EditElement().AddField(field); |
| if (ShouldHourFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeHour12: { |
| DateTimeNumericFieldElement::Step step = |
| CreateStep(kMsPerHour, kMsPerHour * 12); |
| DateTimeFieldElement* field = DateTimeHour12FieldElement::Create( |
| document, EditElement(), hour23_range_, step); |
| EditElement().AddField(field); |
| if (ShouldHourFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeHour23: { |
| DateTimeNumericFieldElement::Step step = |
| CreateStep(kMsPerHour, kMsPerDay); |
| DateTimeFieldElement* field = DateTimeHour23FieldElement::Create( |
| document, EditElement(), hour23_range_, step); |
| EditElement().AddField(field); |
| if (ShouldHourFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeHour24: { |
| DateTimeNumericFieldElement::Step step = |
| CreateStep(kMsPerHour, kMsPerDay); |
| DateTimeFieldElement* field = DateTimeHour24FieldElement::Create( |
| document, EditElement(), hour23_range_, step); |
| EditElement().AddField(field); |
| if (ShouldHourFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeMinute: { |
| DateTimeNumericFieldElement::Step step = |
| CreateStep(kMsPerMinute, kMsPerHour); |
| DateTimeNumericFieldElement* field = DateTimeMinuteFieldElement::Create( |
| document, EditElement(), minute_range_, step); |
| EditElement().AddField(field); |
| if (ShouldMinuteFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeMonth: // Fallthrough. |
| case DateTimeFormat::kFieldTypeMonthStandAlone: { |
| int min_month = 0, max_month = 11; |
| if (parameters_.minimum.GetType() != DateComponents::kInvalid && |
| parameters_.maximum.GetType() != DateComponents::kInvalid && |
| parameters_.minimum.FullYear() == parameters_.maximum.FullYear() && |
| parameters_.minimum.Month() <= parameters_.maximum.Month()) { |
| min_month = parameters_.minimum.Month(); |
| max_month = parameters_.maximum.Month(); |
| } |
| DateTimeFieldElement* field; |
| switch (count) { |
| case kCountForNarrowMonth: // Fallthrough. |
| case kCountForAbbreviatedMonth: |
| field = DateTimeSymbolicMonthFieldElement::Create( |
| document, EditElement(), |
| field_type == DateTimeFormat::kFieldTypeMonth |
| ? parameters_.locale.ShortMonthLabels() |
| : parameters_.locale.ShortStandAloneMonthLabels(), |
| min_month, max_month); |
| break; |
| case kCountForFullMonth: |
| field = DateTimeSymbolicMonthFieldElement::Create( |
| document, EditElement(), |
| field_type == DateTimeFormat::kFieldTypeMonth |
| ? parameters_.locale.MonthLabels() |
| : parameters_.locale.StandAloneMonthLabels(), |
| min_month, max_month); |
| break; |
| default: |
| field = DateTimeMonthFieldElement::Create( |
| document, EditElement(), parameters_.placeholder_for_month, |
| DateTimeNumericFieldElement::Range(min_month + 1, max_month + 1)); |
| break; |
| } |
| EditElement().AddField(field); |
| if (min_month == max_month && min_month == date_value_.Month() && |
| date_value_.GetType() != DateComponents::kMonth) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypePeriod: { |
| DateTimeFieldElement* field = DateTimeAMPMFieldElement::Create( |
| document, EditElement(), parameters_.locale.TimeAMPMLabels()); |
| EditElement().AddField(field); |
| if (ShouldAMPMFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeSecond: { |
| DateTimeNumericFieldElement::Step step = |
| CreateStep(kMsPerSecond, kMsPerMinute); |
| DateTimeNumericFieldElement* field = DateTimeSecondFieldElement::Create( |
| document, EditElement(), second_range_, step); |
| EditElement().AddField(field); |
| if (ShouldSecondFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| |
| if (NeedMillisecondField()) { |
| VisitLiteral(parameters_.locale.LocalizedDecimalSeparator()); |
| VisitField(DateTimeFormat::kFieldTypeFractionalSecond, 3); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeFractionalSecond: { |
| DateTimeNumericFieldElement::Step step = CreateStep(1, kMsPerSecond); |
| DateTimeNumericFieldElement* field = |
| DateTimeMillisecondFieldElement::Create(document, EditElement(), |
| millisecond_range_, step); |
| EditElement().AddField(field); |
| if (ShouldMillisecondFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeWeekOfYear: { |
| DateTimeNumericFieldElement::Range range( |
| DateComponents::kMinimumWeekNumber, |
| DateComponents::kMaximumWeekNumber); |
| if (parameters_.minimum.GetType() != DateComponents::kInvalid && |
| parameters_.maximum.GetType() != DateComponents::kInvalid && |
| parameters_.minimum.FullYear() == parameters_.maximum.FullYear() && |
| parameters_.minimum.Week() <= parameters_.maximum.Week()) { |
| range.minimum = parameters_.minimum.Week(); |
| range.maximum = parameters_.maximum.Week(); |
| } |
| EditElement().AddField( |
| DateTimeWeekFieldElement::Create(document, EditElement(), range)); |
| return; |
| } |
| |
| case DateTimeFormat::kFieldTypeYear: { |
| DateTimeYearFieldElement::Parameters year_params; |
| if (parameters_.minimum.GetType() == DateComponents::kInvalid) { |
| year_params.minimum_year = DateComponents::MinimumYear(); |
| year_params.min_is_specified = false; |
| } else { |
| year_params.minimum_year = parameters_.minimum.FullYear(); |
| year_params.min_is_specified = true; |
| } |
| if (parameters_.maximum.GetType() == DateComponents::kInvalid) { |
| year_params.maximum_year = DateComponents::MaximumYear(); |
| year_params.max_is_specified = false; |
| } else { |
| year_params.maximum_year = parameters_.maximum.FullYear(); |
| year_params.max_is_specified = true; |
| } |
| if (year_params.minimum_year > year_params.maximum_year) { |
| std::swap(year_params.minimum_year, year_params.maximum_year); |
| std::swap(year_params.min_is_specified, year_params.max_is_specified); |
| } |
| year_params.placeholder = parameters_.placeholder_for_year; |
| DateTimeFieldElement* field = DateTimeYearFieldElement::Create( |
| document, EditElement(), year_params); |
| EditElement().AddField(field); |
| if (ShouldYearFieldDisabled()) { |
| field->SetValueAsDate(date_value_); |
| field->SetDisabled(); |
| } |
| return; |
| } |
| |
| default: |
| return; |
| } |
| } |
| |
| bool DateTimeEditBuilder::ShouldAMPMFieldDisabled() const { |
| return ShouldHourFieldDisabled() || |
| (hour23_range_.minimum < 12 && hour23_range_.maximum < 12 && |
| date_value_.Hour() < 12) || |
| (hour23_range_.minimum >= 12 && hour23_range_.maximum >= 12 && |
| date_value_.Hour() >= 12); |
| } |
| |
| bool DateTimeEditBuilder::ShouldDayOfMonthFieldDisabled() const { |
| return day_range_.IsSingleton() && |
| day_range_.minimum == date_value_.MonthDay() && |
| date_value_.GetType() != DateComponents::kDate; |
| } |
| |
| bool DateTimeEditBuilder::ShouldHourFieldDisabled() const { |
| if (hour23_range_.IsSingleton() && |
| hour23_range_.minimum == date_value_.Hour() && |
| !(ShouldMinuteFieldDisabled() && ShouldSecondFieldDisabled() && |
| ShouldMillisecondFieldDisabled())) |
| return true; |
| |
| if (date_value_.GetType() == DateComponents::kTime) |
| return false; |
| DCHECK_EQ(date_value_.GetType(), DateComponents::kDateTimeLocal); |
| |
| if (ShouldDayOfMonthFieldDisabled()) { |
| DCHECK_EQ(parameters_.minimum.FullYear(), parameters_.maximum.FullYear()); |
| DCHECK_EQ(parameters_.minimum.Month(), parameters_.maximum.Month()); |
| return false; |
| } |
| |
| const Decimal decimal_ms_per_day(static_cast<int>(kMsPerDay)); |
| Decimal hour_part_of_minimum = |
| (GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_day) / |
| static_cast<int>(kMsPerHour)) |
| .Floor(); |
| return hour_part_of_minimum == date_value_.Hour() && |
| GetStepRange().Step().Remainder(decimal_ms_per_day).IsZero(); |
| } |
| |
| bool DateTimeEditBuilder::ShouldMillisecondFieldDisabled() const { |
| if (millisecond_range_.IsSingleton() && |
| millisecond_range_.minimum == date_value_.Millisecond()) |
| return true; |
| |
| const Decimal decimal_ms_per_second(static_cast<int>(kMsPerSecond)); |
| return GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_second) == |
| date_value_.Millisecond() && |
| GetStepRange().Step().Remainder(decimal_ms_per_second).IsZero(); |
| } |
| |
| bool DateTimeEditBuilder::ShouldMinuteFieldDisabled() const { |
| if (minute_range_.IsSingleton() && |
| minute_range_.minimum == date_value_.Minute()) |
| return true; |
| |
| const Decimal decimal_ms_per_hour(static_cast<int>(kMsPerHour)); |
| Decimal minute_part_of_minimum = |
| (GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_hour) / |
| static_cast<int>(kMsPerMinute)) |
| .Floor(); |
| return minute_part_of_minimum == date_value_.Minute() && |
| GetStepRange().Step().Remainder(decimal_ms_per_hour).IsZero(); |
| } |
| |
| bool DateTimeEditBuilder::ShouldSecondFieldDisabled() const { |
| if (second_range_.IsSingleton() && |
| second_range_.minimum == date_value_.Second()) |
| return true; |
| |
| const Decimal decimal_ms_per_minute(static_cast<int>(kMsPerMinute)); |
| Decimal second_part_of_minimum = |
| (GetStepRange().StepBase().Abs().Remainder(decimal_ms_per_minute) / |
| static_cast<int>(kMsPerSecond)) |
| .Floor(); |
| return second_part_of_minimum == date_value_.Second() && |
| GetStepRange().Step().Remainder(decimal_ms_per_minute).IsZero(); |
| } |
| |
| bool DateTimeEditBuilder::ShouldYearFieldDisabled() const { |
| return parameters_.minimum.GetType() != DateComponents::kInvalid && |
| parameters_.maximum.GetType() != DateComponents::kInvalid && |
| parameters_.minimum.FullYear() == parameters_.maximum.FullYear() && |
| parameters_.minimum.FullYear() == date_value_.FullYear(); |
| } |
| |
| void DateTimeEditBuilder::VisitLiteral(const String& text) { |
| DEFINE_STATIC_LOCAL(AtomicString, text_pseudo_id, |
| ("-webkit-datetime-edit-text")); |
| DCHECK_GT(text.length(), 0u); |
| HTMLDivElement* element = HTMLDivElement::Create(EditElement().GetDocument()); |
| element->SetShadowPseudoId(text_pseudo_id); |
| if (parameters_.locale.IsRTL() && text.length()) { |
| CharDirection dir = Direction(text[0]); |
| if (dir == kSegmentSeparator || dir == kWhiteSpaceNeutral || |
| dir == kOtherNeutral) |
| element->AppendChild(Text::Create(EditElement().GetDocument(), |
| String(&kRightToLeftMarkCharacter, 1))); |
| } |
| element->AppendChild(Text::Create(EditElement().GetDocument(), text)); |
| EditElement().FieldsWrapperElement()->AppendChild(element); |
| } |
| |
| DateTimeEditElement& DateTimeEditBuilder::EditElement() const { |
| return *edit_element_; |
| } |
| |
| DateTimeNumericFieldElement::Step DateTimeEditBuilder::CreateStep( |
| double ms_per_field_unit, |
| double ms_per_field_size) const { |
| const Decimal ms_per_field_unit_decimal(static_cast<int>(ms_per_field_unit)); |
| const Decimal ms_per_field_size_decimal(static_cast<int>(ms_per_field_size)); |
| Decimal step_milliseconds = GetStepRange().Step(); |
| DCHECK(!ms_per_field_unit_decimal.IsZero()); |
| DCHECK(!ms_per_field_size_decimal.IsZero()); |
| DCHECK(!step_milliseconds.IsZero()); |
| |
| DateTimeNumericFieldElement::Step step(1, 0); |
| |
| if (step_milliseconds.Remainder(ms_per_field_size_decimal).IsZero()) |
| step_milliseconds = ms_per_field_size_decimal; |
| |
| if (ms_per_field_size_decimal.Remainder(step_milliseconds).IsZero() && |
| step_milliseconds.Remainder(ms_per_field_unit_decimal).IsZero()) { |
| step.step = static_cast<int>( |
| (step_milliseconds / ms_per_field_unit_decimal).ToDouble()); |
| step.step_base = static_cast<int>( |
| (GetStepRange().StepBase() / ms_per_field_unit_decimal) |
| .Floor() |
| .Remainder(ms_per_field_size_decimal / ms_per_field_unit_decimal) |
| .ToDouble()); |
| } |
| return step; |
| } |
| |
| // ---------------------------- |
| |
| DateTimeEditElement::EditControlOwner::~EditControlOwner() {} |
| |
| DateTimeEditElement::DateTimeEditElement(Document& document, |
| EditControlOwner& edit_control_owner) |
| : HTMLDivElement(document), edit_control_owner_(&edit_control_owner) { |
| SetHasCustomStyleCallbacks(); |
| } |
| |
| DateTimeEditElement::~DateTimeEditElement() {} |
| |
| DEFINE_TRACE(DateTimeEditElement) { |
| visitor->Trace(fields_); |
| visitor->Trace(edit_control_owner_); |
| HTMLDivElement::Trace(visitor); |
| } |
| |
| inline Element* DateTimeEditElement::FieldsWrapperElement() const { |
| DCHECK(firstChild()); |
| return ToElementOrDie(firstChild()); |
| } |
| |
| void DateTimeEditElement::AddField(DateTimeFieldElement* field) { |
| if (fields_.size() >= kMaximumNumberOfFields) |
| return; |
| fields_.push_back(field); |
| FieldsWrapperElement()->AppendChild(field); |
| } |
| |
| bool DateTimeEditElement::AnyEditableFieldsHaveValues() const { |
| for (const auto& field : fields_) { |
| if (!field->IsDisabled() && field->HasValue()) |
| return true; |
| } |
| return false; |
| } |
| |
| void DateTimeEditElement::BlurByOwner() { |
| if (DateTimeFieldElement* field = FocusedField()) |
| field->blur(); |
| } |
| |
| DateTimeEditElement* DateTimeEditElement::Create( |
| Document& document, |
| EditControlOwner& edit_control_owner) { |
| DateTimeEditElement* container = |
| new DateTimeEditElement(document, edit_control_owner); |
| 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> original_style = OriginalStyleForLayoutObject(); |
| RefPtr<ComputedStyle> style = ComputedStyle::Clone(*original_style); |
| float width = 0; |
| for (Node* child = FieldsWrapperElement()->firstChild(); child; |
| child = child->nextSibling()) { |
| if (!child->IsElementNode()) |
| continue; |
| Element* child_element = ToElement(child); |
| if (child_element->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*>(child_element) |
| ->MaximumWidth(*style); |
| } else { |
| // ::-webkit-datetime-edit-text case. It has no |
| // border/padding/margin in html.css. |
| width += DateTimeFieldElement::ComputeTextWidth( |
| *style, child_element->textContent()); |
| } |
| } |
| style->SetWidth(Length(ceilf(width), kFixed)); |
| style->SetUnique(); |
| return style.Release(); |
| } |
| |
| void DateTimeEditElement::DidBlurFromField(WebFocusType focus_type) { |
| if (edit_control_owner_) |
| edit_control_owner_->DidBlurFromControl(focus_type); |
| } |
| |
| void DateTimeEditElement::DidFocusOnField(WebFocusType focus_type) { |
| if (edit_control_owner_) |
| edit_control_owner_->DidFocusOnControl(focus_type); |
| } |
| |
| void DateTimeEditElement::DisabledStateChanged() { |
| UpdateUIState(); |
| } |
| |
| DateTimeFieldElement* DateTimeEditElement::FieldAt(size_t field_index) const { |
| return field_index < fields_.size() ? fields_[field_index].Get() : 0; |
| } |
| |
| size_t DateTimeEditElement::FieldIndexOf( |
| const DateTimeFieldElement& field) const { |
| for (size_t field_index = 0; field_index < fields_.size(); ++field_index) { |
| if (fields_[field_index] == &field) |
| return field_index; |
| } |
| return kInvalidFieldIndex; |
| } |
| |
| void DateTimeEditElement::FocusIfNoFocus() { |
| if (FocusedFieldIndex() != kInvalidFieldIndex) |
| return; |
| FocusOnNextFocusableField(0); |
| } |
| |
| void DateTimeEditElement::FocusByOwner(Element* old_focused_element) { |
| if (old_focused_element && old_focused_element->IsDateTimeFieldElement()) { |
| DateTimeFieldElement* old_focused_field = |
| static_cast<DateTimeFieldElement*>(old_focused_element); |
| size_t index = FieldIndexOf(*old_focused_field); |
| GetDocument().UpdateStyleAndLayoutTreeForNode(old_focused_field); |
| if (index != kInvalidFieldIndex && old_focused_field->IsFocusable()) { |
| old_focused_field->focus(); |
| return; |
| } |
| } |
| FocusOnNextFocusableField(0); |
| } |
| |
| DateTimeFieldElement* DateTimeEditElement::FocusedField() const { |
| return FieldAt(FocusedFieldIndex()); |
| } |
| |
| size_t DateTimeEditElement::FocusedFieldIndex() const { |
| Element* const focused_field_element = GetDocument().FocusedElement(); |
| for (size_t field_index = 0; field_index < fields_.size(); ++field_index) { |
| if (fields_[field_index] == focused_field_element) |
| return field_index; |
| } |
| return kInvalidFieldIndex; |
| } |
| |
| void DateTimeEditElement::FieldValueChanged() { |
| if (edit_control_owner_) |
| edit_control_owner_->EditControlValueChanged(); |
| } |
| |
| bool DateTimeEditElement::FocusOnNextFocusableField(size_t start_index) { |
| GetDocument().UpdateStyleAndLayoutTreeIgnorePendingStylesheets(); |
| for (size_t field_index = start_index; field_index < fields_.size(); |
| ++field_index) { |
| if (fields_[field_index]->IsFocusable()) { |
| fields_[field_index]->focus(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool DateTimeEditElement::FocusOnNextField(const DateTimeFieldElement& field) { |
| const size_t start_field_index = FieldIndexOf(field); |
| if (start_field_index == kInvalidFieldIndex) |
| return false; |
| return FocusOnNextFocusableField(start_field_index + 1); |
| } |
| |
| bool DateTimeEditElement::FocusOnPreviousField( |
| const DateTimeFieldElement& field) { |
| const size_t start_field_index = FieldIndexOf(field); |
| if (start_field_index == kInvalidFieldIndex) |
| return false; |
| GetDocument().UpdateStyleAndLayoutTreeIgnorePendingStylesheets(); |
| size_t field_index = start_field_index; |
| while (field_index > 0) { |
| --field_index; |
| if (fields_[field_index]->IsFocusable()) { |
| fields_[field_index]->focus(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool DateTimeEditElement::IsDateTimeEditElement() const { |
| return true; |
| } |
| |
| bool DateTimeEditElement::IsDisabled() const { |
| return edit_control_owner_ && |
| edit_control_owner_->IsEditControlOwnerDisabled(); |
| } |
| |
| bool DateTimeEditElement::IsFieldOwnerDisabled() const { |
| return IsDisabled(); |
| } |
| |
| bool DateTimeEditElement::IsFieldOwnerReadOnly() const { |
| return IsReadOnly(); |
| } |
| |
| bool DateTimeEditElement::IsReadOnly() const { |
| return edit_control_owner_ && |
| edit_control_owner_->IsEditControlOwnerReadOnly(); |
| } |
| |
| void DateTimeEditElement::GetLayout(const LayoutParameters& layout_parameters, |
| const DateComponents& date_value) { |
| // TODO(tkent): We assume this function never dispatches events. However this |
| // can dispatch 'blur' event in Node::removeChild(). |
| |
| DEFINE_STATIC_LOCAL(AtomicString, fields_wrapper_pseudo_id, |
| ("-webkit-datetime-edit-fields-wrapper")); |
| if (!HasChildren()) { |
| HTMLDivElement* element = HTMLDivElement::Create(GetDocument()); |
| element->SetShadowPseudoId(fields_wrapper_pseudo_id); |
| AppendChild(element); |
| } |
| Element* fields_wrapper = FieldsWrapperElement(); |
| |
| size_t focused_field_index = this->FocusedFieldIndex(); |
| DateTimeFieldElement* const focused_field = FieldAt(focused_field_index); |
| const AtomicString focused_field_id = |
| focused_field ? focused_field->ShadowPseudoId() : g_null_atom; |
| |
| DateTimeEditBuilder builder(*this, layout_parameters, date_value); |
| Node* last_child_to_be_removed = fields_wrapper->lastChild(); |
| if (!builder.Build(layout_parameters.date_time_format) || fields_.IsEmpty()) { |
| last_child_to_be_removed = fields_wrapper->lastChild(); |
| builder.Build(layout_parameters.fallback_date_time_format); |
| } |
| |
| if (focused_field_index != kInvalidFieldIndex) { |
| for (size_t field_index = 0; field_index < fields_.size(); ++field_index) { |
| if (fields_[field_index]->ShadowPseudoId() == focused_field_id) { |
| focused_field_index = field_index; |
| break; |
| } |
| } |
| if (DateTimeFieldElement* field = |
| FieldAt(std::min(focused_field_index, fields_.size() - 1))) |
| field->focus(); |
| } |
| |
| if (last_child_to_be_removed) { |
| for (Node* child_node = fields_wrapper->firstChild(); child_node; |
| child_node = fields_wrapper->firstChild()) { |
| fields_wrapper->RemoveChild(child_node); |
| if (child_node == last_child_to_be_removed) |
| break; |
| } |
| SetNeedsStyleRecalc( |
| kSubtreeStyleChange, |
| StyleChangeReasonForTracing::Create(StyleChangeReason::kControl)); |
| } |
| } |
| |
| AtomicString DateTimeEditElement::LocaleIdentifier() const { |
| return edit_control_owner_ ? edit_control_owner_->LocaleIdentifier() |
| : g_null_atom; |
| } |
| |
| void DateTimeEditElement::FieldDidChangeValueByKeyboard() { |
| if (edit_control_owner_) |
| edit_control_owner_->EditControlDidChangeValueByKeyboard(); |
| } |
| |
| void DateTimeEditElement::ReadOnlyStateChanged() { |
| UpdateUIState(); |
| } |
| |
| void DateTimeEditElement::ResetFields() { |
| for (const auto& field : fields_) |
| field->RemoveEventHandler(); |
| 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& layout_parameters, |
| const DateComponents& date) { |
| GetLayout(layout_parameters, date); |
| for (const auto& field : fields_) |
| field->SetValueAsDate(date); |
| } |
| |
| void DateTimeEditElement::SetValueAsDateTimeFieldsState( |
| const DateTimeFieldsState& date_time_fields_state) { |
| for (const auto& field : fields_) |
| field->SetValueAsDateTimeFieldsState(date_time_fields_state); |
| } |
| |
| void DateTimeEditElement::SetEmptyValue( |
| const LayoutParameters& layout_parameters, |
| const DateComponents& date_for_read_only_field) { |
| GetLayout(layout_parameters, date_for_read_only_field); |
| for (const auto& field : fields_) |
| field->SetEmptyValue(DateTimeFieldElement::kDispatchNoEvent); |
| } |
| |
| bool DateTimeEditElement::HasFocusedField() { |
| return FocusedFieldIndex() != kInvalidFieldIndex; |
| } |
| |
| void DateTimeEditElement::SetOnlyYearMonthDay(const DateComponents& date) { |
| DCHECK_EQ(date.GetType(), DateComponents::kDate); |
| |
| if (!edit_control_owner_) |
| return; |
| |
| DateTimeFieldsState date_time_fields_state = ValueAsDateTimeFieldsState(); |
| date_time_fields_state.SetYear(date.FullYear()); |
| date_time_fields_state.SetMonth(date.Month() + 1); |
| date_time_fields_state.SetDayOfMonth(date.MonthDay()); |
| SetValueAsDateTimeFieldsState(date_time_fields_state); |
| edit_control_owner_->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 (!edit_control_owner_) |
| return g_empty_string; |
| return edit_control_owner_->FormatDateTimeFieldsState( |
| ValueAsDateTimeFieldsState()); |
| } |
| |
| DateTimeFieldsState DateTimeEditElement::ValueAsDateTimeFieldsState() const { |
| DateTimeFieldsState date_time_fields_state; |
| for (const auto& field : fields_) |
| field->PopulateDateTimeFieldsState(date_time_fields_state); |
| return date_time_fields_state; |
| } |
| |
| } // namespace blink |