Home | History | Annotate | Download | only in date
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ash/system/date/date_view.h"
      6 
      7 #include "ash/shell.h"
      8 #include "ash/system/tray/system_tray_delegate.h"
      9 #include "ash/system/tray/tray_constants.h"
     10 #include "ash/system/tray/tray_utils.h"
     11 #include "base/i18n/rtl.h"
     12 #include "base/i18n/time_formatting.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "base/time/time.h"
     15 #include "grit/ash_strings.h"
     16 #include "third_party/icu/source/i18n/unicode/datefmt.h"
     17 #include "third_party/icu/source/i18n/unicode/dtptngen.h"
     18 #include "third_party/icu/source/i18n/unicode/smpdtfmt.h"
     19 #include "ui/base/l10n/l10n_util.h"
     20 #include "ui/views/border.h"
     21 #include "ui/views/controls/label.h"
     22 #include "ui/views/layout/box_layout.h"
     23 #include "ui/views/layout/grid_layout.h"
     24 #include "ui/views/widget/widget.h"
     25 
     26 namespace ash {
     27 namespace internal {
     28 namespace tray {
     29 
     30 namespace {
     31 
     32 // Amount of slop to add into the timer to make sure we're into the next minute
     33 // when the timer goes off.
     34 const int kTimerSlopSeconds = 1;
     35 
     36 // Text color of the vertical clock minutes.
     37 const SkColor kVerticalClockMinuteColor = SkColorSetRGB(0xBA, 0xBA, 0xBA);
     38 
     39 // Padding between the left edge of the shelf and the left edge of the vertical
     40 // clock.
     41 const int kVerticalClockLeftPadding = 9;
     42 
     43 // Offset used to bring the minutes line closer to the hours line in the
     44 // vertical clock.
     45 const int kVerticalClockMinutesTopOffset = -4;
     46 
     47 base::string16 FormatDate(const base::Time& time) {
     48   icu::UnicodeString date_string;
     49   scoped_ptr<icu::DateFormat> formatter(
     50       icu::DateFormat::createDateInstance(icu::DateFormat::kMedium));
     51   formatter->format(static_cast<UDate>(time.ToDoubleT() * 1000), date_string);
     52   return base::string16(date_string.getBuffer(),
     53                   static_cast<size_t>(date_string.length()));
     54 }
     55 
     56 base::string16 FormatDayOfWeek(const base::Time& time) {
     57   UErrorCode status = U_ZERO_ERROR;
     58   scoped_ptr<icu::DateTimePatternGenerator> generator(
     59       icu::DateTimePatternGenerator::createInstance(status));
     60   DCHECK(U_SUCCESS(status));
     61   const char kBasePattern[] = "EEE";
     62   icu::UnicodeString generated_pattern =
     63       generator->getBestPattern(icu::UnicodeString(kBasePattern), status);
     64   DCHECK(U_SUCCESS(status));
     65   icu::SimpleDateFormat simple_formatter(generated_pattern, status);
     66   DCHECK(U_SUCCESS(status));
     67   icu::UnicodeString date_string;
     68   simple_formatter.format(
     69       static_cast<UDate>(time.ToDoubleT() * 1000), date_string, status);
     70   DCHECK(U_SUCCESS(status));
     71   return base::string16(
     72       date_string.getBuffer(), static_cast<size_t>(date_string.length()));
     73 }
     74 
     75 views::Label* CreateLabel() {
     76   views::Label* label = new views::Label;
     77   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     78   label->SetBackgroundColor(SkColorSetARGB(0, 255, 255, 255));
     79   return label;
     80 }
     81 
     82 }  // namespace
     83 
     84 BaseDateTimeView::~BaseDateTimeView() {
     85   timer_.Stop();
     86 }
     87 
     88 void BaseDateTimeView::UpdateText() {
     89   base::Time now = base::Time::Now();
     90   UpdateTextInternal(now);
     91   SchedulePaint();
     92   SetTimer(now);
     93 }
     94 
     95 BaseDateTimeView::BaseDateTimeView() {
     96   SetTimer(base::Time::Now());
     97 }
     98 
     99 void BaseDateTimeView::SetTimer(const base::Time& now) {
    100   // Try to set the timer to go off at the next change of the minute. We don't
    101   // want to have the timer go off more than necessary since that will cause
    102   // the CPU to wake up and consume power.
    103   base::Time::Exploded exploded;
    104   now.LocalExplode(&exploded);
    105 
    106   // Often this will be called at minute boundaries, and we'll actually want
    107   // 60 seconds from now.
    108   int seconds_left = 60 - exploded.second;
    109   if (seconds_left == 0)
    110     seconds_left = 60;
    111 
    112   // Make sure that the timer fires on the next minute. Without this, if it is
    113   // called just a teeny bit early, then it will skip the next minute.
    114   seconds_left += kTimerSlopSeconds;
    115 
    116   timer_.Stop();
    117   timer_.Start(
    118       FROM_HERE, base::TimeDelta::FromSeconds(seconds_left),
    119       this, &BaseDateTimeView::UpdateText);
    120 }
    121 
    122 void BaseDateTimeView::ChildPreferredSizeChanged(views::View* child) {
    123   PreferredSizeChanged();
    124 }
    125 
    126 void BaseDateTimeView::OnLocaleChanged() {
    127   UpdateText();
    128 }
    129 
    130 DateView::DateView()
    131     : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
    132                  GetHourClockType()),
    133       actionable_(false) {
    134   SetLayoutManager(
    135       new views::BoxLayout(
    136           views::BoxLayout::kVertical, 0, 0, 0));
    137   date_label_ = CreateLabel();
    138   date_label_->SetEnabledColor(kHeaderTextColorNormal);
    139   UpdateTextInternal(base::Time::Now());
    140   AddChildView(date_label_);
    141   SetFocusable(actionable_);
    142 }
    143 
    144 DateView::~DateView() {
    145 }
    146 
    147 void DateView::SetActionable(bool actionable) {
    148   actionable_ = actionable;
    149   SetFocusable(actionable_);
    150 }
    151 
    152 void DateView::UpdateTimeFormat() {
    153   hour_type_ =
    154       ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
    155   UpdateText();
    156 }
    157 
    158 void DateView::UpdateTextInternal(const base::Time& now) {
    159   SetAccessibleName(
    160       base::TimeFormatFriendlyDate(now) +
    161       ASCIIToUTF16(", ") +
    162       base::TimeFormatTimeOfDayWithHourClockType(
    163           now, hour_type_, base::kKeepAmPm));
    164   date_label_->SetText(
    165       l10n_util::GetStringFUTF16(
    166           IDS_ASH_STATUS_TRAY_DATE, FormatDayOfWeek(now), FormatDate(now)));
    167 }
    168 
    169 bool DateView::PerformAction(const ui::Event& event) {
    170   if (!actionable_)
    171     return false;
    172 
    173   ash::Shell::GetInstance()->system_tray_delegate()->ShowDateSettings();
    174   return true;
    175 }
    176 
    177 void DateView::OnMouseEntered(const ui::MouseEvent& event) {
    178   if (!actionable_)
    179     return;
    180   date_label_->SetEnabledColor(kHeaderTextColorHover);
    181   SchedulePaint();
    182 }
    183 
    184 void DateView::OnMouseExited(const ui::MouseEvent& event) {
    185   if (!actionable_)
    186     return;
    187   date_label_->SetEnabledColor(kHeaderTextColorNormal);
    188   SchedulePaint();
    189 }
    190 
    191 ///////////////////////////////////////////////////////////////////////////////
    192 
    193 TimeView::TimeView(TrayDate::ClockLayout clock_layout)
    194     : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
    195                  GetHourClockType()) {
    196   SetupLabels();
    197   UpdateTextInternal(base::Time::Now());
    198   UpdateClockLayout(clock_layout);
    199   SetFocusable(false);
    200 }
    201 
    202 TimeView::~TimeView() {
    203 }
    204 
    205 void TimeView::UpdateTimeFormat() {
    206   hour_type_ =
    207       ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
    208   UpdateText();
    209 }
    210 
    211 void TimeView::UpdateTextInternal(const base::Time& now) {
    212   // Just in case |now| is null, do NOT update time; otherwise, it will
    213   // crash icu code by calling into base::TimeFormatTimeOfDayWithHourClockType,
    214   // see details in crbug.com/147570.
    215   if (now.is_null()) {
    216     LOG(ERROR) << "Received null value from base::Time |now| in argument";
    217     return;
    218   }
    219 
    220   base::string16 current_time = base::TimeFormatTimeOfDayWithHourClockType(
    221       now, hour_type_, base::kDropAmPm);
    222   horizontal_label_->SetText(current_time);
    223   horizontal_label_->SetTooltipText(base::TimeFormatFriendlyDate(now));
    224 
    225   // Calculate vertical clock layout labels.
    226   size_t colon_pos = current_time.find(ASCIIToUTF16(":"));
    227   base::string16 hour = current_time.substr(0, colon_pos);
    228   base::string16 minute = current_time.substr(colon_pos + 1);
    229 
    230   // Sometimes pad single-digit hours with a zero for aesthetic reasons.
    231   if (hour.length() == 1 &&
    232       hour_type_ == base::k24HourClock &&
    233       !base::i18n::IsRTL())
    234     hour = ASCIIToUTF16("0") + hour;
    235 
    236   vertical_label_hours_->SetText(hour);
    237   vertical_label_minutes_->SetText(minute);
    238   Layout();
    239 }
    240 
    241 bool TimeView::PerformAction(const ui::Event& event) {
    242   return false;
    243 }
    244 
    245 bool TimeView::OnMousePressed(const ui::MouseEvent& event) {
    246   // Let the event fall through.
    247   return false;
    248 }
    249 
    250 void TimeView::UpdateClockLayout(TrayDate::ClockLayout clock_layout){
    251   SetBorder(clock_layout);
    252   if (clock_layout == TrayDate::HORIZONTAL_CLOCK) {
    253     RemoveChildView(vertical_label_hours_.get());
    254     RemoveChildView(vertical_label_minutes_.get());
    255     SetLayoutManager(
    256         new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
    257     AddChildView(horizontal_label_.get());
    258   } else {
    259     RemoveChildView(horizontal_label_.get());
    260     views::GridLayout* layout = new views::GridLayout(this);
    261     SetLayoutManager(layout);
    262     const int kColumnId = 0;
    263     views::ColumnSet* columns = layout->AddColumnSet(kColumnId);
    264     columns->AddPaddingColumn(0, kVerticalClockLeftPadding);
    265     columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
    266                        0, views::GridLayout::USE_PREF, 0, 0);
    267     layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment);
    268     layout->StartRow(0, kColumnId);
    269     layout->AddView(vertical_label_hours_.get());
    270     layout->StartRow(0, kColumnId);
    271     layout->AddView(vertical_label_minutes_.get());
    272     layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment);
    273   }
    274   Layout();
    275 }
    276 
    277 void TimeView::SetBorder(TrayDate::ClockLayout clock_layout) {
    278   if (clock_layout == TrayDate::HORIZONTAL_CLOCK)
    279     set_border(views::Border::CreateEmptyBorder(
    280         0, kTrayLabelItemHorizontalPaddingBottomAlignment,
    281         0, kTrayLabelItemHorizontalPaddingBottomAlignment));
    282   else
    283     set_border(NULL);
    284 }
    285 
    286 void TimeView::SetupLabels() {
    287   horizontal_label_.reset(CreateLabel());
    288   SetupLabel(horizontal_label_.get());
    289   vertical_label_hours_.reset(CreateLabel());
    290   SetupLabel(vertical_label_hours_.get());
    291   vertical_label_minutes_.reset(CreateLabel());
    292   SetupLabel(vertical_label_minutes_.get());
    293   vertical_label_minutes_->SetEnabledColor(kVerticalClockMinuteColor);
    294   // Pull the minutes up closer to the hours by using a negative top border.
    295   vertical_label_minutes_->set_border(
    296       views::Border::CreateEmptyBorder(
    297           kVerticalClockMinutesTopOffset, 0, 0, 0));
    298 }
    299 
    300 void TimeView::SetupLabel(views::Label* label) {
    301   label->set_owned_by_client();
    302   SetupLabelForTray(label);
    303   gfx::Font font = label->font();
    304   label->SetFont(font.DeriveFont(0, font.GetStyle() & ~gfx::Font::BOLD));
    305 }
    306 
    307 }  // namespace tray
    308 }  // namespace internal
    309 }  // namespace ash
    310