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