• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
FormatDate(const base::Time & time)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 
FormatDayOfWeek(const base::Time & time)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 
CreateLabel()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 
~BaseDateTimeView()82 BaseDateTimeView::~BaseDateTimeView() {
83   timer_.Stop();
84 }
85 
UpdateText()86 void BaseDateTimeView::UpdateText() {
87   base::Time now = base::Time::Now();
88   UpdateTextInternal(now);
89   SchedulePaint();
90   SetTimer(now);
91 }
92 
BaseDateTimeView()93 BaseDateTimeView::BaseDateTimeView() {
94   SetTimer(base::Time::Now());
95 }
96 
SetTimer(const base::Time & now)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 
ChildPreferredSizeChanged(views::View * child)120 void BaseDateTimeView::ChildPreferredSizeChanged(views::View* child) {
121   PreferredSizeChanged();
122 }
123 
OnLocaleChanged()124 void BaseDateTimeView::OnLocaleChanged() {
125   UpdateText();
126 }
127 
DateView()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 
~DateView()142 DateView::~DateView() {
143 }
144 
SetAction(TrayDate::DateAction action)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 
UpdateTimeFormat()158 void DateView::UpdateTimeFormat() {
159   hour_type_ =
160       ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
161   UpdateText();
162 }
163 
GetHourTypeForTesting() const164 base::HourClockType DateView::GetHourTypeForTesting() const {
165   return hour_type_;
166 }
167 
UpdateTextInternal(const base::Time & now)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 
PerformAction(const ui::Event & event)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 
OnMouseEntered(const ui::MouseEvent & event)189 void DateView::OnMouseEntered(const ui::MouseEvent& event) {
190   if (action_ == TrayDate::NONE)
191     return;
192   date_label_->SetEnabledColor(kHeaderTextColorHover);
193   SchedulePaint();
194 }
195 
OnMouseExited(const ui::MouseEvent & event)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 
TimeView(TrayDate::ClockLayout clock_layout)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 
~TimeView()214 TimeView::~TimeView() {
215 }
216 
UpdateTimeFormat()217 void TimeView::UpdateTimeFormat() {
218   hour_type_ =
219       ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
220   UpdateText();
221 }
222 
GetHourTypeForTesting() const223 base::HourClockType TimeView::GetHourTypeForTesting() const {
224   return hour_type_;
225 }
226 
UpdateTextInternal(const base::Time & now)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 
PerformAction(const ui::Event & event)257 bool TimeView::PerformAction(const ui::Event& event) {
258   return false;
259 }
260 
OnMousePressed(const ui::MouseEvent & event)261 bool TimeView::OnMousePressed(const ui::MouseEvent& event) {
262   // Let the event fall through.
263   return false;
264 }
265 
UpdateClockLayout(TrayDate::ClockLayout clock_layout)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 
SetBorderFromLayout(TrayDate::ClockLayout clock_layout)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 
SetupLabels()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 
SetupLabel(views::Label * label)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