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.
5 #include "ash/system/date/date_view.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"
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;
34 // Text color of the vertical clock minutes.
35 const SkColor kVerticalClockMinuteColor
= SkColorSetRGB(0xBA, 0xBA, 0xBA);
37 // Padding between the left edge of the shelf and the left edge of the vertical
39 const int kVerticalClockLeftPadding
= 9;
41 // Offset used to bring the minutes line closer to the hours line in the
43 const int kVerticalClockMinutesTopOffset
= -4;
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()));
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()));
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));
82 BaseDateTimeView::~BaseDateTimeView() {
86 void BaseDateTimeView::UpdateText() {
87 base::Time now
= base::Time::Now();
88 UpdateTextInternal(now
);
93 BaseDateTimeView::BaseDateTimeView() {
94 SetTimer(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
);
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)
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
;
116 FROM_HERE
, base::TimeDelta::FromSeconds(seconds_left
),
117 this, &BaseDateTimeView::UpdateText
);
120 void BaseDateTimeView::ChildPreferredSizeChanged(views::View
* child
) {
121 PreferredSizeChanged();
124 void BaseDateTimeView::OnLocaleChanged() {
129 : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
131 action_(TrayDate::NONE
) {
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_
);
142 DateView::~DateView() {
145 void DateView::SetAction(TrayDate::DateAction action
) {
146 if (action
== action_
)
148 if (IsMouseHovered()) {
149 date_label_
->SetEnabledColor(
150 action
== TrayDate::NONE
? kHeaderTextColorNormal
:
151 kHeaderTextColorHover
);
155 SetFocusable(action_
!= TrayDate::NONE
);
158 void DateView::UpdateTimeFormat() {
160 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
164 base::HourClockType
DateView::GetHourTypeForTesting() const {
168 void DateView::UpdateTextInternal(const base::Time
& now
) {
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
)));
179 bool DateView::PerformAction(const ui::Event
& event
) {
180 if (action_
== TrayDate::NONE
)
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();
189 void DateView::OnMouseEntered(const ui::MouseEvent
& event
) {
190 if (action_
== TrayDate::NONE
)
192 date_label_
->SetEnabledColor(kHeaderTextColorHover
);
196 void DateView::OnMouseExited(const ui::MouseEvent
& event
) {
197 if (action_
== TrayDate::NONE
)
199 date_label_
->SetEnabledColor(kHeaderTextColorNormal
);
203 ///////////////////////////////////////////////////////////////////////////////
205 TimeView::TimeView(TrayDate::ClockLayout clock_layout
)
206 : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
207 GetHourClockType()) {
209 UpdateTextInternal(base::Time::Now());
210 UpdateClockLayout(clock_layout
);
214 TimeView::~TimeView() {
217 void TimeView::UpdateTimeFormat() {
219 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
223 base::HourClockType
TimeView::GetHourTypeForTesting() const {
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.
232 LOG(ERROR
) << "Received null value from base::Time |now| in argument";
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
));
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);
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
;
252 vertical_label_hours_
->SetText(hour
);
253 vertical_label_minutes_
->SetText(minute
);
257 bool TimeView::PerformAction(const ui::Event
& event
) {
261 bool TimeView::OnMousePressed(const ui::MouseEvent
& event
) {
262 // Let the event fall through.
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());
272 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 0));
273 AddChildView(horizontal_label_
.get());
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
);
293 void TimeView::SetBorderFromLayout(TrayDate::ClockLayout clock_layout
) {
294 if (clock_layout
== TrayDate::HORIZONTAL_CLOCK
)
295 SetBorder(views::Border::CreateEmptyBorder(
297 kTrayLabelItemHorizontalPaddingBottomAlignment
,
299 kTrayLabelItemHorizontalPaddingBottomAlignment
));
301 SetBorder(views::Border::NullBorder());
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));
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
));