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/command_line.h"
12 #include "base/i18n/rtl.h"
13 #include "base/i18n/time_formatting.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/time/time.h"
16 #include "grit/ash_strings.h"
17 #include "third_party/icu/source/i18n/unicode/datefmt.h"
18 #include "third_party/icu/source/i18n/unicode/dtptngen.h"
19 #include "third_party/icu/source/i18n/unicode/smpdtfmt.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/ui_base_switches_util.h"
22 #include "ui/views/border.h"
23 #include "ui/views/controls/label.h"
24 #include "ui/views/layout/box_layout.h"
25 #include "ui/views/layout/grid_layout.h"
26 #include "ui/views/widget/widget.h"
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;
36 // Text color of the vertical clock minutes.
37 const SkColor kVerticalClockMinuteColor
= SkColorSetRGB(0xBA, 0xBA, 0xBA);
39 // Padding between the left edge of the shelf and the left edge of the vertical
41 const int kVerticalClockLeftPadding
= 9;
43 // Offset used to bring the minutes line closer to the hours line in the
45 const int kVerticalClockMinutesTopOffset
= -4;
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()));
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()));
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));
84 BaseDateTimeView::~BaseDateTimeView() {
88 void BaseDateTimeView::UpdateText() {
89 base::Time now
= base::Time::Now();
90 UpdateTextInternal(now
);
95 BaseDateTimeView::BaseDateTimeView() {
96 SetTimer(base::Time::Now());
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
);
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)
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
;
118 FROM_HERE
, base::TimeDelta::FromSeconds(seconds_left
),
119 this, &BaseDateTimeView::UpdateText
);
122 void BaseDateTimeView::ChildPreferredSizeChanged(views::View
* child
) {
123 PreferredSizeChanged();
126 void BaseDateTimeView::OnLocaleChanged() {
131 : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
133 action_(TrayDate::NONE
) {
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_
);
144 DateView::~DateView() {
147 void DateView::SetAction(TrayDate::DateAction action
) {
148 if (action
== action_
)
150 if (IsMouseHovered()) {
151 date_label_
->SetEnabledColor(
152 action
== TrayDate::NONE
? kHeaderTextColorNormal
:
153 kHeaderTextColorHover
);
157 SetFocusable(action_
!= TrayDate::NONE
);
160 void DateView::UpdateTimeFormat() {
162 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
166 base::HourClockType
DateView::GetHourTypeForTesting() const {
170 void DateView::SetActive(bool active
) {
171 date_label_
->SetEnabledColor(active
? kHeaderTextColorHover
172 : kHeaderTextColorNormal
);
176 void DateView::UpdateTextInternal(const base::Time
& now
) {
178 base::TimeFormatFriendlyDate(now
) +
179 base::ASCIIToUTF16(", ") +
180 base::TimeFormatTimeOfDayWithHourClockType(
181 now
, hour_type_
, base::kKeepAmPm
));
182 date_label_
->SetText(
183 l10n_util::GetStringFUTF16(
184 IDS_ASH_STATUS_TRAY_DATE
, FormatDayOfWeek(now
), FormatDate(now
)));
187 bool DateView::PerformAction(const ui::Event
& event
) {
188 if (action_
== TrayDate::NONE
)
190 if (action_
== TrayDate::SHOW_DATE_SETTINGS
)
191 ash::Shell::GetInstance()->system_tray_delegate()->ShowDateSettings();
192 else if (action_
== TrayDate::SET_SYSTEM_TIME
)
193 ash::Shell::GetInstance()->system_tray_delegate()->ShowSetTimeDialog();
197 void DateView::OnMouseEntered(const ui::MouseEvent
& event
) {
198 if (action_
== TrayDate::NONE
)
203 void DateView::OnMouseExited(const ui::MouseEvent
& event
) {
204 if (action_
== TrayDate::NONE
)
209 void DateView::OnGestureEvent(ui::GestureEvent
* event
) {
210 if (switches::IsTouchFeedbackEnabled()) {
211 if (event
->type() == ui::ET_GESTURE_TAP_DOWN
) {
213 } else if (event
->type() == ui::ET_GESTURE_TAP_CANCEL
||
214 event
->type() == ui::ET_GESTURE_END
) {
218 BaseDateTimeView::OnGestureEvent(event
);
221 ///////////////////////////////////////////////////////////////////////////////
223 TimeView::TimeView(TrayDate::ClockLayout clock_layout
)
224 : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
225 GetHourClockType()) {
227 UpdateTextInternal(base::Time::Now());
228 UpdateClockLayout(clock_layout
);
232 TimeView::~TimeView() {
235 void TimeView::UpdateTimeFormat() {
237 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
241 base::HourClockType
TimeView::GetHourTypeForTesting() const {
245 void TimeView::UpdateTextInternal(const base::Time
& now
) {
246 // Just in case |now| is null, do NOT update time; otherwise, it will
247 // crash icu code by calling into base::TimeFormatTimeOfDayWithHourClockType,
248 // see details in crbug.com/147570.
250 LOG(ERROR
) << "Received null value from base::Time |now| in argument";
254 base::string16 current_time
= base::TimeFormatTimeOfDayWithHourClockType(
255 now
, hour_type_
, base::kDropAmPm
);
256 horizontal_label_
->SetText(current_time
);
257 horizontal_label_
->SetTooltipText(base::TimeFormatFriendlyDate(now
));
259 // Calculate vertical clock layout labels.
260 size_t colon_pos
= current_time
.find(base::ASCIIToUTF16(":"));
261 base::string16 hour
= current_time
.substr(0, colon_pos
);
262 base::string16 minute
= current_time
.substr(colon_pos
+ 1);
264 // Sometimes pad single-digit hours with a zero for aesthetic reasons.
265 if (hour
.length() == 1 &&
266 hour_type_
== base::k24HourClock
&&
267 !base::i18n::IsRTL())
268 hour
= base::ASCIIToUTF16("0") + hour
;
270 vertical_label_hours_
->SetText(hour
);
271 vertical_label_minutes_
->SetText(minute
);
275 bool TimeView::PerformAction(const ui::Event
& event
) {
279 bool TimeView::OnMousePressed(const ui::MouseEvent
& event
) {
280 // Let the event fall through.
284 void TimeView::UpdateClockLayout(TrayDate::ClockLayout clock_layout
) {
285 SetBorderFromLayout(clock_layout
);
286 if (clock_layout
== TrayDate::HORIZONTAL_CLOCK
) {
287 RemoveChildView(vertical_label_hours_
.get());
288 RemoveChildView(vertical_label_minutes_
.get());
290 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 0));
291 AddChildView(horizontal_label_
.get());
293 RemoveChildView(horizontal_label_
.get());
294 views::GridLayout
* layout
= new views::GridLayout(this);
295 SetLayoutManager(layout
);
296 const int kColumnId
= 0;
297 views::ColumnSet
* columns
= layout
->AddColumnSet(kColumnId
);
298 columns
->AddPaddingColumn(0, kVerticalClockLeftPadding
);
299 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::CENTER
,
300 0, views::GridLayout::USE_PREF
, 0, 0);
301 layout
->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment
);
302 layout
->StartRow(0, kColumnId
);
303 layout
->AddView(vertical_label_hours_
.get());
304 layout
->StartRow(0, kColumnId
);
305 layout
->AddView(vertical_label_minutes_
.get());
306 layout
->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment
);
311 void TimeView::SetBorderFromLayout(TrayDate::ClockLayout clock_layout
) {
312 if (clock_layout
== TrayDate::HORIZONTAL_CLOCK
)
313 SetBorder(views::Border::CreateEmptyBorder(
315 kTrayLabelItemHorizontalPaddingBottomAlignment
,
317 kTrayLabelItemHorizontalPaddingBottomAlignment
));
319 SetBorder(views::Border::NullBorder());
322 void TimeView::SetupLabels() {
323 horizontal_label_
.reset(CreateLabel());
324 SetupLabel(horizontal_label_
.get());
325 vertical_label_hours_
.reset(CreateLabel());
326 SetupLabel(vertical_label_hours_
.get());
327 vertical_label_minutes_
.reset(CreateLabel());
328 SetupLabel(vertical_label_minutes_
.get());
329 vertical_label_minutes_
->SetEnabledColor(kVerticalClockMinuteColor
);
330 // Pull the minutes up closer to the hours by using a negative top border.
331 vertical_label_minutes_
->SetBorder(views::Border::CreateEmptyBorder(
332 kVerticalClockMinutesTopOffset
, 0, 0, 0));
335 void TimeView::SetupLabel(views::Label
* label
) {
336 label
->set_owned_by_client();
337 SetupLabelForTray(label
);
338 label
->SetElideBehavior(gfx::NO_ELIDE
);