Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ash / system / date / date_view.cc
blobcdb2a5b07deabee1507b6d464e0d45a6d169445d
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"
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/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"
28 namespace ash {
29 namespace tray {
30 namespace {
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
40 // clock.
41 const int kVerticalClockLeftPadding = 9;
43 // Offset used to bring the minutes line closer to the hours line in the
44 // vertical clock.
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));
79 return label;
82 } // namespace
84 BaseDateTimeView::~BaseDateTimeView() {
85 timer_.Stop();
88 void BaseDateTimeView::UpdateText() {
89 base::Time now = base::Time::Now();
90 UpdateTextInternal(now);
91 SchedulePaint();
92 SetTimer(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)
110 seconds_left = 60;
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;
116 timer_.Stop();
117 timer_.Start(
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() {
127 UpdateText();
130 DateView::DateView()
131 : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
132 GetHourClockType()),
133 action_(TrayDate::NONE) {
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(false);
144 DateView::~DateView() {
147 void DateView::SetAction(TrayDate::DateAction action) {
148 if (action == action_)
149 return;
150 if (IsMouseHovered()) {
151 date_label_->SetEnabledColor(
152 action == TrayDate::NONE ? kHeaderTextColorNormal :
153 kHeaderTextColorHover);
154 SchedulePaint();
156 action_ = action;
157 SetFocusable(action_ != TrayDate::NONE);
160 void DateView::UpdateTimeFormat() {
161 hour_type_ =
162 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
163 UpdateText();
166 base::HourClockType DateView::GetHourTypeForTesting() const {
167 return hour_type_;
170 void DateView::SetActive(bool active) {
171 date_label_->SetEnabledColor(active ? kHeaderTextColorHover
172 : kHeaderTextColorNormal);
173 SchedulePaint();
176 void DateView::UpdateTextInternal(const base::Time& now) {
177 SetAccessibleName(
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)
189 return false;
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();
194 return true;
197 void DateView::OnMouseEntered(const ui::MouseEvent& event) {
198 if (action_ == TrayDate::NONE)
199 return;
200 SetActive(true);
203 void DateView::OnMouseExited(const ui::MouseEvent& event) {
204 if (action_ == TrayDate::NONE)
205 return;
206 SetActive(false);
209 void DateView::OnGestureEvent(ui::GestureEvent* event) {
210 if (switches::IsTouchFeedbackEnabled()) {
211 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
212 SetActive(true);
213 } else if (event->type() == ui::ET_GESTURE_TAP_CANCEL ||
214 event->type() == ui::ET_GESTURE_END) {
215 SetActive(false);
218 BaseDateTimeView::OnGestureEvent(event);
221 ///////////////////////////////////////////////////////////////////////////////
223 TimeView::TimeView(TrayDate::ClockLayout clock_layout)
224 : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
225 GetHourClockType()) {
226 SetupLabels();
227 UpdateTextInternal(base::Time::Now());
228 UpdateClockLayout(clock_layout);
229 SetFocusable(false);
232 TimeView::~TimeView() {
235 void TimeView::UpdateTimeFormat() {
236 hour_type_ =
237 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
238 UpdateText();
241 base::HourClockType TimeView::GetHourTypeForTesting() const {
242 return hour_type_;
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.
249 if (now.is_null()) {
250 LOG(ERROR) << "Received null value from base::Time |now| in argument";
251 return;
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);
272 Layout();
275 bool TimeView::PerformAction(const ui::Event& event) {
276 return false;
279 bool TimeView::OnMousePressed(const ui::MouseEvent& event) {
280 // Let the event fall through.
281 return false;
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());
289 SetLayoutManager(
290 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
291 AddChildView(horizontal_label_.get());
292 } else {
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);
308 Layout();
311 void TimeView::SetBorderFromLayout(TrayDate::ClockLayout clock_layout) {
312 if (clock_layout == TrayDate::HORIZONTAL_CLOCK)
313 SetBorder(views::Border::CreateEmptyBorder(
315 kTrayLabelItemHorizontalPaddingBottomAlignment,
317 kTrayLabelItemHorizontalPaddingBottomAlignment));
318 else
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);
341 } // namespace tray
342 } // namespace ash