text: // 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_views.h"
  #include "base/i18n/time_formatting.h"
  #include "base/time.h"
  #include "base/utf_string_conversions.h"
  #include "grit/ash_strings.h"
  #include "third_party/icu/public/i18n/unicode/datefmt.h"
  #include "third_party/icu/public/i18n/unicode/dtptngen.h"
  #include "third_party/icu/public/i18n/unicode/smpdtfmt.h"
  #include "ui/base/l10n/l10n_util.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;
  
  // Top number text color of vertical clock.
  const SkColor kVerticalClockHourColor = SkColorSetRGB(0xBA, 0xBA, 0xBA);
  
  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() : 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_);
    set_focusable(actionable_);
  }
  
  DateView::~DateView() {
  }
  
  void DateView::SetActionable(bool actionable) {
    actionable_ = actionable;
    set_focusable(actionable_);
  }
  
  void DateView::UpdateTextInternal(const base::Time& now) {
    SetAccessibleName(
        base::TimeFormatFriendlyDate(now) +
        ASCIIToUTF16(",") +
        base::TimeFormatTimeOfDayWithHourClockType(
            now, base::k12HourClock, 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);
    set_focusable(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);
    label_->SetText(current_time);
    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);
    label_hour_left_->SetText(hour.substr(0, 1));
    label_hour_right_->SetText(hour.length() == 2 ?
        hour.substr(1,1) : ASCIIToUTF16(":"));
    label_minute_left_->SetText(minute.substr(0, 1));
    label_minute_right_->SetText(minute.substr(1, 1));
  
    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(label_hour_left_.get());
      RemoveChildView(label_hour_right_.get());
      RemoveChildView(label_minute_left_.get());
      RemoveChildView(label_minute_right_.get());
      SetLayoutManager(
          new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
      AddChildView(label_.get());
    } else {
      RemoveChildView(label_.get());
      views::GridLayout* layout = new views::GridLayout(this);
      SetLayoutManager(layout);
      views::ColumnSet* columns = layout->AddColumnSet(0);
      columns->AddPaddingColumn(0, 6);
      columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
                         0, views::GridLayout::USE_PREF, 0, 0);
      columns->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
                         0, views::GridLayout::USE_PREF, 0, 0);
      layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVeriticalAlignment);
      layout->StartRow(0, 0);
      layout->AddView(label_hour_left_.get());
      layout->AddView(label_hour_right_.get());
      layout->StartRow(0, 0);
      layout->AddView(label_minute_left_.get());
      layout->AddView(label_minute_right_.get());
      layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVeriticalAlignment);
    }
    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() {
    label_.reset(CreateLabel());
    SetupLabel(label_.get());
    label_hour_left_.reset(CreateLabel());
    SetupLabel(label_hour_left_.get());
    label_hour_right_.reset(CreateLabel());
    SetupLabel(label_hour_right_.get());
    label_minute_left_.reset(CreateLabel());
    SetupLabel(label_minute_left_.get());
    label_minute_right_.reset(CreateLabel());
    SetupLabel(label_minute_right_.get());
    label_hour_left_->SetEnabledColor(kVerticalClockHourColor);
    label_hour_right_->SetEnabledColor(kVerticalClockHourColor);
  }
  
  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
  