普通文本  |  310行  |  9.82 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/system/date/date_view.h"

#include "ash/shell.h"
#include "ash/system/tray/system_tray_delegate.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_utils.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 "grit/ash_strings.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/base/l10n/l10n_util.h"
#include "ui/views/border.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/widget/widget.h"

namespace ash {
namespace internal {
namespace tray {

namespace {

// Amount of slop to add into the timer to make sure we're into the next minute
// when the timer goes off.
const int kTimerSlopSeconds = 1;

// Text color of the vertical clock minutes.
const SkColor kVerticalClockMinuteColor = SkColorSetRGB(0xBA, 0xBA, 0xBA);

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

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

base::string16 FormatDate(const base::Time& time) {
  icu::UnicodeString date_string;
  scoped_ptr<icu::DateFormat> formatter(
      icu::DateFormat::createDateInstance(icu::DateFormat::kMedium));
  formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string);
  return base::string16(date_string.getBuffer(),
                  static_cast<size_t>(date_string.length()));
}

base::string16 FormatDayOfWeek(const base::Time& time) {
  UErrorCode status = U_ZERO_ERROR;
  scoped_ptr<icu::DateTimePatternGenerator> generator(
      icu::DateTimePatternGenerator::createInstance(status));
  DCHECK(U_SUCCESS(status));
  const char kBasePattern[] = "EEE";
  icu::UnicodeString generated_pattern =
      generator->getBestPattern(icu::UnicodeString(kBasePattern), status);
  DCHECK(U_SUCCESS(status));
  icu::SimpleDateFormat simple_formatter(generated_pattern, status);
  DCHECK(U_SUCCESS(status));
  icu::UnicodeString date_string;
  simple_formatter.format(
      static_cast<UDate>(time.ToDoubleT() * 1000), date_string, status);
  DCHECK(U_SUCCESS(status));
  return base::string16(
      date_string.getBuffer(), static_cast<size_t>(date_string.length()));
}

views::Label* CreateLabel() {
  views::Label* label = new views::Label;
  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  label->SetBackgroundColor(SkColorSetARGB(0, 255, 255, 255));
  return label;
}

}  // namespace

BaseDateTimeView::~BaseDateTimeView() {
  timer_.Stop();
}

void BaseDateTimeView::UpdateText() {
  base::Time now = base::Time::Now();
  UpdateTextInternal(now);
  SchedulePaint();
  SetTimer(now);
}

BaseDateTimeView::BaseDateTimeView() {
  SetTimer(base::Time::Now());
}

void BaseDateTimeView::SetTimer(const base::Time& now) {
  // Try to set the timer to go off at the next change of the minute. We don't
  // want to have the timer go off more than necessary since that will cause
  // the CPU to wake up and consume power.
  base::Time::Exploded exploded;
  now.LocalExplode(&exploded);

  // Often this will be called at minute boundaries, and we'll actually want
  // 60 seconds from now.
  int seconds_left = 60 - exploded.second;
  if (seconds_left == 0)
    seconds_left = 60;

  // Make sure that the timer fires on the next minute. Without this, if it is
  // called just a teeny bit early, then it will skip the next minute.
  seconds_left += kTimerSlopSeconds;

  timer_.Stop();
  timer_.Start(
      FROM_HERE, base::TimeDelta::FromSeconds(seconds_left),
      this, &BaseDateTimeView::UpdateText);
}

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

void BaseDateTimeView::OnLocaleChanged() {
  UpdateText();
}

DateView::DateView()
    : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
                 GetHourClockType()),
      actionable_(false) {
  SetLayoutManager(
      new views::BoxLayout(
          views::BoxLayout::kVertical, 0, 0, 0));
  date_label_ = CreateLabel();
  date_label_->SetEnabledColor(kHeaderTextColorNormal);
  UpdateTextInternal(base::Time::Now());
  AddChildView(date_label_);
  SetFocusable(actionable_);
}

DateView::~DateView() {
}

void DateView::SetActionable(bool actionable) {
  actionable_ = actionable;
  SetFocusable(actionable_);
}

void DateView::UpdateTimeFormat() {
  hour_type_ =
      ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
  UpdateText();
}

void DateView::UpdateTextInternal(const base::Time& now) {
  SetAccessibleName(
      base::TimeFormatFriendlyDate(now) +
      ASCIIToUTF16(", ") +
      base::TimeFormatTimeOfDayWithHourClockType(
          now, hour_type_, base::kKeepAmPm));
  date_label_->SetText(
      l10n_util::GetStringFUTF16(
          IDS_ASH_STATUS_TRAY_DATE, FormatDayOfWeek(now), FormatDate(now)));
}

bool DateView::PerformAction(const ui::Event& event) {
  if (!actionable_)
    return false;

  ash::Shell::GetInstance()->system_tray_delegate()->ShowDateSettings();
  return true;
}

void DateView::OnMouseEntered(const ui::MouseEvent& event) {
  if (!actionable_)
    return;
  date_label_->SetEnabledColor(kHeaderTextColorHover);
  SchedulePaint();
}

void DateView::OnMouseExited(const ui::MouseEvent& event) {
  if (!actionable_)
    return;
  date_label_->SetEnabledColor(kHeaderTextColorNormal);
  SchedulePaint();
}

///////////////////////////////////////////////////////////////////////////////

TimeView::TimeView(TrayDate::ClockLayout clock_layout)
    : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
                 GetHourClockType()) {
  SetupLabels();
  UpdateTextInternal(base::Time::Now());
  UpdateClockLayout(clock_layout);
  SetFocusable(false);
}

TimeView::~TimeView() {
}

void TimeView::UpdateTimeFormat() {
  hour_type_ =
      ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
  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;
  }

  base::string16 current_time = base::TimeFormatTimeOfDayWithHourClockType(
      now, hour_type_, base::kDropAmPm);
  horizontal_label_->SetText(current_time);
  horizontal_label_->SetTooltipText(base::TimeFormatFriendlyDate(now));

  // Calculate vertical clock layout labels.
  size_t colon_pos = current_time.find(ASCIIToUTF16(":"));
  base::string16 hour = current_time.substr(0, colon_pos);
  base::string16 minute = current_time.substr(colon_pos + 1);

  // Sometimes pad single-digit hours with a zero for aesthetic reasons.
  if (hour.length() == 1 &&
      hour_type_ == base::k24HourClock &&
      !base::i18n::IsRTL())
    hour = ASCIIToUTF16("0") + hour;

  vertical_label_hours_->SetText(hour);
  vertical_label_minutes_->SetText(minute);
  Layout();
}

bool TimeView::PerformAction(const ui::Event& event) {
  return false;
}

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

void TimeView::UpdateClockLayout(TrayDate::ClockLayout clock_layout){
  SetBorder(clock_layout);
  if (clock_layout == TrayDate::HORIZONTAL_CLOCK) {
    RemoveChildView(vertical_label_hours_.get());
    RemoveChildView(vertical_label_minutes_.get());
    SetLayoutManager(
        new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
    AddChildView(horizontal_label_.get());
  } else {
    RemoveChildView(horizontal_label_.get());
    views::GridLayout* layout = new views::GridLayout(this);
    SetLayoutManager(layout);
    const int kColumnId = 0;
    views::ColumnSet* columns = layout->AddColumnSet(kColumnId);
    columns->AddPaddingColumn(0, kVerticalClockLeftPadding);
    columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
                       0, views::GridLayout::USE_PREF, 0, 0);
    layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment);
    layout->StartRow(0, kColumnId);
    layout->AddView(vertical_label_hours_.get());
    layout->StartRow(0, kColumnId);
    layout->AddView(vertical_label_minutes_.get());
    layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment);
  }
  Layout();
}

void TimeView::SetBorder(TrayDate::ClockLayout clock_layout) {
  if (clock_layout == TrayDate::HORIZONTAL_CLOCK)
    set_border(views::Border::CreateEmptyBorder(
        0, kTrayLabelItemHorizontalPaddingBottomAlignment,
        0, kTrayLabelItemHorizontalPaddingBottomAlignment));
  else
    set_border(NULL);
}

void TimeView::SetupLabels() {
  horizontal_label_.reset(CreateLabel());
  SetupLabel(horizontal_label_.get());
  vertical_label_hours_.reset(CreateLabel());
  SetupLabel(vertical_label_hours_.get());
  vertical_label_minutes_.reset(CreateLabel());
  SetupLabel(vertical_label_minutes_.get());
  vertical_label_minutes_->SetEnabledColor(kVerticalClockMinuteColor);
  // Pull the minutes up closer to the hours by using a negative top border.
  vertical_label_minutes_->set_border(
      views::Border::CreateEmptyBorder(
          kVerticalClockMinutesTopOffset, 0, 0, 0));
}

void TimeView::SetupLabel(views::Label* label) {
  label->set_owned_by_client();
  SetupLabelForTray(label);
  gfx::Font font = label->font();
  label->SetFont(font.DeriveFont(0, font.GetStyle() & ~gfx::Font::BOLD));
}

}  // namespace tray
}  // namespace internal
}  // namespace ash