910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/time/time_view.h"

#include <memory>

#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/model/clock_model.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/time/calendar_utils.h"
#include "ash/system/time/time_view_utils.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/tray/tray_utils.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/i18n/rtl.h"
#include "base/i18n/time_formatting.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chromeos/constants/chromeos_features.h"
#include "third_party/icu/source/i18n/unicode/datefmt.h"
#include "third_party/icu/source/i18n/unicode/dtptngen.h"
#include "third_party/icu/source/i18n/unicode/smpdtfmt.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_id.h"
#include "ui/events/event.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout_view.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"

namespace ash {
namespace {

// Padding between the left edge of the shelf and the left edge of the vertical
// clock.
const int kVerticalClockLeftPadding = 9;

// Padding between the left/right edge of the shelf and the left edge of the
// vertical clock with date.
const int kVerticalDateClockHorizontalPadding = 8;

// Padding on top/bottom of the vertical clock date view.
const int kVerticalDateVerticalPadding = 2;

// How much size smaller the text in the date view compare to the text size of
// the clock view.
const int kDateTextSizeDiff = 4;

// Offset used to bring the minutes line closer to the hours line in the
// vertical clock.
const int kVerticalClockMinutesTopOffset = -2;

std::u16string FormatDate(const base::Time& time) {
  // Use 'short' month format (e.g., "Oct") followed by non-padded day of
  // month (e.g., "2", "10").
  return base::LocalizedTimeFormatWithPattern(time, "LLLd");
}

// Returns the time to show by the time view.
base::Time GetTimeToShow() {
  if (!switches::IsStabilizeTimeDependentViewForTestsEnabled())
    return base::Time::Now();

  // The code below only runs in tests.
  static base::Time fixed_time;
  if (fixed_time.is_null())
    CHECK(base::Time::FromString(kFakeNowTimeStringInPixelTest, &fixed_time));

  return fixed_time;
}

}  // namespace

VerticalDateView::VerticalDateView()
    : icon_(AddChildView(std::make_unique<views::ImageView>())),
      text_label_(AddChildView(std::make_unique<views::Label>())) {
  SetLayoutManager(std::make_unique<views::FillLayout>());
  text_label_->SetSubpixelRenderingEnabled(false);
  text_label_->SetAutoColorReadabilityEnabled(false);
  text_label_->SetFontList(
      gfx::FontList().Derive(kTrayTextFontSizeIncrease - kDateTextSizeDiff,
                             gfx::Font::NORMAL, gfx::Font::Weight::BOLD));
  text_label_->SetElideBehavior(gfx::NO_ELIDE);
  UpdateText();
  text_label_->SetBorder(views::CreateEmptyBorder(
      gfx::Insets::TLBR(kVerticalDateVerticalPadding, 0, 0, 0)));
  UpdateIconAndLabelColorId(cros_tokens::kCrosSysOnSurface);
}

VerticalDateView::~VerticalDateView() = default;

void VerticalDateView::UpdateText() {
  const base::Time time_to_show = GetTimeToShow();
  const std::u16string new_text = calendar_utils::GetDayIntOfMonth(
      time_to_show + calendar_utils::GetTimeDifference(time_to_show));
  if (text_label_->GetText() == new_text)
    return;
  text_label_->SetText(new_text);
  text_label_->SetCustomTooltipText(base::TimeFormatFriendlyDate(time_to_show));
}

void VerticalDateView::UpdateIconAndLabelColorId(ui::ColorId color_id) {
  text_label_->SetEnabledColor(color_id);
  icon_->SetImage(
      ui::ImageModel::FromVectorIcon(kCalendarBackgroundIcon, color_id));
}

BEGIN_METADATA(VerticalDateView)
END_METADATA

TimeView::TimeView(ClockLayout clock_layout, ClockModel* model, Type type)
    : model_(model), type_(type) {
  SetTimer(GetTimeToShow());
  SetFocusBehavior(FocusBehavior::NEVER);
  model_->AddObserver(this);

  SetLayoutManager(std::make_unique<views::FillLayout>());
  switch (type_) {
    case kTime:
      SetupSubviews(clock_layout);
      break;
    case kDate:
      SetupDateviews(clock_layout);
      break;
  }
  // Set role before updating text to ensure that AccessibilityPaintChecks don't
  // fail.
  GetViewAccessibility().SetRole(ax::mojom::Role::kTime);

  UpdateTextInternal(GetTimeToShow());
}

TimeView::~TimeView() {
  model_->RemoveObserver(this);
  timer_.Stop();
}

void TimeView::UpdateClockLayout(ClockLayout clock_layout) {
  const bool horizontal_views_visible =
      clock_layout == ClockLayout::HORIZONTAL_CLOCK;
  switch (type_) {
    case kDate: {
      // Do nothing if the layout hasn't changed.
      if (clock_layout == ClockLayout::HORIZONTAL_CLOCK
              ? horizontal_date_label_container_->GetVisible()
              : vertical_date_view_container_->GetVisible()) {
        return;
      }
      vertical_date_view_container_->SetVisible(!horizontal_views_visible);
      vertical_date_view_->SetVisible(!horizontal_views_visible);
      horizontal_date_label_container_->SetVisible(horizontal_views_visible);
      horizontal_date_label_->SetVisible(horizontal_views_visible);
      break;
    }
    case kTime: {
      // Do nothing if the layout hasn't changed.
      if (clock_layout == ClockLayout::HORIZONTAL_CLOCK
              ? horizontal_time_label_container_->GetVisible()
              : vertical_time_label_container_->GetVisible()) {
        return;
      }
      vertical_time_label_container_->SetVisible(!horizontal_views_visible);
      vertical_label_hours_->SetVisible(!horizontal_views_visible);
      vertical_label_minutes_->SetVisible(!horizontal_views_visible);
      horizontal_time_label_container_->SetVisible(horizontal_views_visible);
      horizontal_time_label_->SetVisible(horizontal_views_visible);
      break;
    }
  }
  DeprecatedLayoutImmediately();
}

void TimeView::SetTextColorId(ui::ColorId color_id,
                              bool auto_color_readability_enabled) {
  auto set_color_id = [&](views::Label* label) {
    label->SetEnabledColor(color_id);
    label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled);
  };

  switch (type_) {
    case kTime:
      set_color_id(horizontal_time_label_);
      set_color_id(vertical_label_hours_);
      set_color_id(vertical_label_minutes_);
      return;
    case kDate:
      set_color_id(horizontal_date_label_);
  }
}

void TimeView::SetTextColor(SkColor color,
                            bool auto_color_readability_enabled) {
  auto set_color = [&](views::Label* label) {
    label->SetEnabledColor(color);
    label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled);
  };

  switch (type_) {
    case kTime:
      set_color(horizontal_time_label_);
      set_color(vertical_label_hours_);
      set_color(vertical_label_minutes_);
      return;
    case kDate:
      set_color(horizontal_date_label_);
  }
}

void TimeView::SetTextFont(const gfx::FontList& font_list) {
  switch (type_) {
    case kTime:
      horizontal_time_label_->SetFontList(font_list);
      vertical_label_hours_->SetFontList(font_list);
      vertical_label_minutes_->SetFontList(font_list);
      return;
    case kDate:
      horizontal_date_label_->SetFontList(font_list);
  }
}

void TimeView::SetTextShadowValues(const gfx::ShadowValues& shadows) {
  switch (type_) {
    case kTime:
      horizontal_time_label_->SetShadows(shadows);
      vertical_label_hours_->SetShadows(shadows);
      vertical_label_minutes_->SetShadows(shadows);
      return;
    case kDate:
      horizontal_date_label_->SetShadows(shadows);
  }
}

void TimeView::SetDateViewColorId(ui::ColorId color_id) {
  if (vertical_date_view_) {
    vertical_date_view_->UpdateIconAndLabelColorId(color_id);
  }
}

void TimeView::OnDateFormatChanged() {
  UpdateTimeFormat();
}

void TimeView::OnSystemClockTimeUpdated() {
  UpdateTimeFormat();
}

void TimeView::OnSystemClockCanSetTimeChanged(bool can_set_time) {}

void TimeView::Refresh() {
  UpdateText();
}

base::HourClockType TimeView::GetHourTypeForTesting() const {
  return model_->hour_clock_type();
}

void TimeView::ChildPreferredSizeChanged(views::View* child) {
  PreferredSizeChanged();
}

bool TimeView::OnMousePressed(const ui::MouseEvent& event) {
  // Let the event fall through.
  return false;
}

void TimeView::OnGestureEvent(ui::GestureEvent* event) {
  // Skip gesture handling happening in Button so that the container views
  // receive and handle them properly.
}

void TimeView::UpdateText() {
  const base::Time now = GetTimeToShow();
  UpdateTextInternal(now);
  SchedulePaint();
  SetTimer(now);
}

void TimeView::UpdateTimeFormat() {
  UpdateText();
}

void TimeView::SetAmPmClockType(base::AmPmClockType am_pm_clock_type) {
  if (am_pm_clock_type_ != am_pm_clock_type) {
    am_pm_clock_type_ = am_pm_clock_type;
    UpdateText();
  }
}

void TimeView::UpdateTextInternal(const base::Time& now) {
  // Just in case |now| is null, do NOT update time; otherwise, it will
  // crash icu code by calling into base::TimeFormatTimeOfDayWithHourClockType,
  // see details in crbug.com/147570.
  if (now.is_null()) {
    LOG(ERROR) << "Received null value from base::Time |now| in argument";
    return;
  }
  const std::u16string friendly_format_date = base::TimeFormatFriendlyDate(now);
  GetViewAccessibility().SetName(
      base::TimeFormatTimeOfDayWithHourClockType(now, model_->hour_clock_type(),
                                                 base::kKeepAmPm) +
      u", " + friendly_format_date);

  switch (type_) {
    case kTime: {
      // Calculate horizontal clock layout label.
      const std::u16string current_time =
          base::TimeFormatTimeOfDayWithHourClockType(
              now, model_->hour_clock_type(), am_pm_clock_type_);

      const bool label_length_changed =
          horizontal_time_label_->GetText().length() != current_time.length();
      horizontal_time_label_->SetText(current_time);
      horizontal_time_label_->SetCustomTooltipText(friendly_format_date);

      // Calculate vertical clock layout labels.
      std::u16string current_hours =
          (model_->hour_clock_type() == base::k24HourClock)
              ? calendar_utils::GetTwentyFourHourClockHours(now)
              : calendar_utils::GetTwelveHourClockHours(now);
      const std::u16string current_minutes = calendar_utils::GetMinutes(now);

      vertical_label_hours_->SetText(current_hours);
      vertical_label_minutes_->SetText(current_minutes);

      DeprecatedLayoutImmediately();

      // When the `new_label` text does not have the some length as the
      // old one's, the layout size of this time view changes as well.
      if (label_length_changed)
        PreferredSizeChanged();

      return;
    }
    case kDate: {
      const std::u16string current_date = FormatDate(now);
      horizontal_date_label_->SetText(current_date);
      horizontal_date_label_->SetCustomTooltipText(friendly_format_date);
      vertical_date_view_->UpdateText();
    }
  }
}

void TimeView::SetupDateviews(ClockLayout clock_layout) {
  DCHECK_EQ(type_, kDate);

  auto horizontal_date_label_container = std::make_unique<View>();
  horizontal_date_label_container->SetLayoutManager(
      std::make_unique<views::FillLayout>());
  horizontal_date_label_container->SetBorder(
      views::CreateEmptyBorder(gfx::Insets::TLBR(
          kUnifiedTrayTextTopPadding, kUnifiedTrayTimeLeftPadding, 0, 0)));

  horizontal_date_label_ = horizontal_date_label_container->AddChildView(
      std::make_unique<views::Label>());
  SetupLabel(horizontal_date_label_);

  const bool horizontal_visible = clock_layout == ClockLayout::HORIZONTAL_CLOCK;
  horizontal_date_label_container->SetVisible(horizontal_visible);
  horizontal_date_label_->SetVisible(horizontal_visible);
  horizontal_date_label_container_ =
      AddChildView(std::move(horizontal_date_label_container));

  auto vertical_date_view_container = std::make_unique<View>();
  vertical_date_view_container->SetLayoutManager(
      std::make_unique<views::FillLayout>());
  vertical_date_view_ = vertical_date_view_container->AddChildView(
      std::make_unique<VerticalDateView>());
  vertical_date_view_->SetBorder(
      views::CreateEmptyBorder(gfx::Insets::TLBR(0, 0, 0, 0)));

  vertical_date_view_container->SetVisible(!horizontal_visible);
  vertical_date_view_->SetVisible(!horizontal_visible);
  vertical_date_view_container_ =
      AddChildView(std::move(vertical_date_view_container));
}

void TimeView::SetupSubviews(ClockLayout clock_layout) {
  DCHECK_EQ(type_, kTime);

  auto horizontal_time_label_container = std::make_unique<View>();
  horizontal_time_label_container->SetLayoutManager(
      std::make_unique<views::FillLayout>());
  horizontal_time_label_container->SetBorder(
      views::CreateEmptyBorder(gfx::Insets::TLBR(
          kUnifiedTrayTextTopPadding, kUnifiedTrayTimeLeftPadding, 0, 0)));
  horizontal_time_label_ = horizontal_time_label_container->AddChildView(
      std::make_unique<views::Label>());
  SetupLabel(horizontal_time_label_);

  const bool horizontal_visible = clock_layout == ClockLayout::HORIZONTAL_CLOCK;
  horizontal_time_label_container->SetVisible(horizontal_visible);
  horizontal_time_label_->SetVisible(horizontal_visible);
  horizontal_time_label_container_ =
      AddChildView(std::move(horizontal_time_label_container));

  auto vertical_time_label_container =
      views::Builder<views::FlexLayoutView>()
          .SetOrientation(views::LayoutOrientation::kVertical)
          .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
          .SetCrossAxisAlignment(views::LayoutAlignment::kEnd)
          .SetInteriorMargin(gfx::Insets::TLBR(
              0, kVerticalClockLeftPadding, kVerticalClockMinutesTopOffset, 0))
          .Build();

  vertical_label_hours_ = vertical_time_label_container->AddChildView(
      std::make_unique<views::Label>());
  SetupLabel(vertical_label_hours_);
  vertical_label_hours_->SetBorder(views::CreateEmptyBorder(
      gfx::Insets::VH(0, kVerticalDateClockHorizontalPadding)));

  vertical_label_minutes_ = vertical_time_label_container->AddChildView(
      std::make_unique<views::Label>());
  SetupLabel(vertical_label_minutes_);
  // Pull the minutes up closer to the hours by using a negative top border.
  vertical_label_minutes_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
      kVerticalClockMinutesTopOffset, kVerticalDateClockHorizontalPadding, 0,
      kVerticalDateClockHorizontalPadding)));
  vertical_time_label_container->SetVisible(!horizontal_visible);
  vertical_label_hours_->SetVisible(!horizontal_visible);
  vertical_label_minutes_->SetVisible(!horizontal_visible);
  vertical_time_label_container_ =
      AddChildView(std::move(vertical_time_label_container));
}

void TimeView::SetupLabel(views::Label* label) {
  SetupLabelForTray(label);
  label->SetElideBehavior(gfx::NO_ELIDE);
}

void TimeView::SetTimer(const base::Time& now) {
  timer_.Stop();
  timer_.Start(FROM_HERE, time_view_utils::GetTimeRemainingToNextMinute(now),
               this, &TimeView::UpdateText);
}

BEGIN_METADATA(TimeView)
END_METADATA

}  // namespace ash