• 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 internal {
28 namespace tray {
29 
30 namespace {
31 
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;
35 
36 // Text color of the vertical clock minutes.
37 const SkColor kVerticalClockMinuteColor = SkColorSetRGB(0xBA, 0xBA, 0xBA);
38 
39 // Padding between the left edge of the shelf and the left edge of the vertical
40 // clock.
41 const int kVerticalClockLeftPadding = 9;
42 
43 // Offset used to bring the minutes line closer to the hours line in the
44 // vertical clock.
45 const int kVerticalClockMinutesTopOffset = -4;
46 
FormatDate(const base::Time & time)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()));
54 }
55 
FormatDayOfWeek(const base::Time & time)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()));
73 }
74 
CreateLabel()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;
80 }
81 
82 }  // namespace
83 
~BaseDateTimeView()84 BaseDateTimeView::~BaseDateTimeView() {
85   timer_.Stop();
86 }
87 
UpdateText()88 void BaseDateTimeView::UpdateText() {
89   base::Time now = base::Time::Now();
90   UpdateTextInternal(now);
91   SchedulePaint();
92   SetTimer(now);
93 }
94 
BaseDateTimeView()95 BaseDateTimeView::BaseDateTimeView() {
96   SetTimer(base::Time::Now());
97 }
98 
SetTimer(const 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);
105 
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;
111 
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;
115 
116   timer_.Stop();
117   timer_.Start(
118       FROM_HERE, base::TimeDelta::FromSeconds(seconds_left),
119       this, &BaseDateTimeView::UpdateText);
120 }
121 
ChildPreferredSizeChanged(views::View * child)122 void BaseDateTimeView::ChildPreferredSizeChanged(views::View* child) {
123   PreferredSizeChanged();
124 }
125 
OnLocaleChanged()126 void BaseDateTimeView::OnLocaleChanged() {
127   UpdateText();
128 }
129 
DateView()130 DateView::DateView()
131     : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
132                  GetHourClockType()),
133       actionable_(false) {
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(actionable_);
142 }
143 
~DateView()144 DateView::~DateView() {
145 }
146 
SetActionable(bool actionable)147 void DateView::SetActionable(bool actionable) {
148   actionable_ = actionable;
149   SetFocusable(actionable_);
150 }
151 
UpdateTimeFormat()152 void DateView::UpdateTimeFormat() {
153   hour_type_ =
154       ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
155   UpdateText();
156 }
157 
UpdateTextInternal(const base::Time & now)158 void DateView::UpdateTextInternal(const base::Time& now) {
159   SetAccessibleName(
160       base::TimeFormatFriendlyDate(now) +
161       ASCIIToUTF16(", ") +
162       base::TimeFormatTimeOfDayWithHourClockType(
163           now, hour_type_, base::kKeepAmPm));
164   date_label_->SetText(
165       l10n_util::GetStringFUTF16(
166           IDS_ASH_STATUS_TRAY_DATE, FormatDayOfWeek(now), FormatDate(now)));
167 }
168 
PerformAction(const ui::Event & event)169 bool DateView::PerformAction(const ui::Event& event) {
170   if (!actionable_)
171     return false;
172 
173   ash::Shell::GetInstance()->system_tray_delegate()->ShowDateSettings();
174   return true;
175 }
176 
OnMouseEntered(const ui::MouseEvent & event)177 void DateView::OnMouseEntered(const ui::MouseEvent& event) {
178   if (!actionable_)
179     return;
180   date_label_->SetEnabledColor(kHeaderTextColorHover);
181   SchedulePaint();
182 }
183 
OnMouseExited(const ui::MouseEvent & event)184 void DateView::OnMouseExited(const ui::MouseEvent& event) {
185   if (!actionable_)
186     return;
187   date_label_->SetEnabledColor(kHeaderTextColorNormal);
188   SchedulePaint();
189 }
190 
191 ///////////////////////////////////////////////////////////////////////////////
192 
TimeView(TrayDate::ClockLayout clock_layout)193 TimeView::TimeView(TrayDate::ClockLayout clock_layout)
194     : hour_type_(ash::Shell::GetInstance()->system_tray_delegate()->
195                  GetHourClockType()) {
196   SetupLabels();
197   UpdateTextInternal(base::Time::Now());
198   UpdateClockLayout(clock_layout);
199   SetFocusable(false);
200 }
201 
~TimeView()202 TimeView::~TimeView() {
203 }
204 
UpdateTimeFormat()205 void TimeView::UpdateTimeFormat() {
206   hour_type_ =
207       ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType();
208   UpdateText();
209 }
210 
UpdateTextInternal(const base::Time & now)211 void TimeView::UpdateTextInternal(const base::Time& now) {
212   // Just in case |now| is null, do NOT update time; otherwise, it will
213   // crash icu code by calling into base::TimeFormatTimeOfDayWithHourClockType,
214   // see details in crbug.com/147570.
215   if (now.is_null()) {
216     LOG(ERROR) << "Received null value from base::Time |now| in argument";
217     return;
218   }
219 
220   base::string16 current_time = base::TimeFormatTimeOfDayWithHourClockType(
221       now, hour_type_, base::kDropAmPm);
222   horizontal_label_->SetText(current_time);
223   horizontal_label_->SetTooltipText(base::TimeFormatFriendlyDate(now));
224 
225   // Calculate vertical clock layout labels.
226   size_t colon_pos = current_time.find(ASCIIToUTF16(":"));
227   base::string16 hour = current_time.substr(0, colon_pos);
228   base::string16 minute = current_time.substr(colon_pos + 1);
229 
230   // Sometimes pad single-digit hours with a zero for aesthetic reasons.
231   if (hour.length() == 1 &&
232       hour_type_ == base::k24HourClock &&
233       !base::i18n::IsRTL())
234     hour = ASCIIToUTF16("0") + hour;
235 
236   vertical_label_hours_->SetText(hour);
237   vertical_label_minutes_->SetText(minute);
238   Layout();
239 }
240 
PerformAction(const ui::Event & event)241 bool TimeView::PerformAction(const ui::Event& event) {
242   return false;
243 }
244 
OnMousePressed(const ui::MouseEvent & event)245 bool TimeView::OnMousePressed(const ui::MouseEvent& event) {
246   // Let the event fall through.
247   return false;
248 }
249 
UpdateClockLayout(TrayDate::ClockLayout clock_layout)250 void TimeView::UpdateClockLayout(TrayDate::ClockLayout clock_layout){
251   SetBorder(clock_layout);
252   if (clock_layout == TrayDate::HORIZONTAL_CLOCK) {
253     RemoveChildView(vertical_label_hours_.get());
254     RemoveChildView(vertical_label_minutes_.get());
255     SetLayoutManager(
256         new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
257     AddChildView(horizontal_label_.get());
258   } else {
259     RemoveChildView(horizontal_label_.get());
260     views::GridLayout* layout = new views::GridLayout(this);
261     SetLayoutManager(layout);
262     const int kColumnId = 0;
263     views::ColumnSet* columns = layout->AddColumnSet(kColumnId);
264     columns->AddPaddingColumn(0, kVerticalClockLeftPadding);
265     columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
266                        0, views::GridLayout::USE_PREF, 0, 0);
267     layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment);
268     layout->StartRow(0, kColumnId);
269     layout->AddView(vertical_label_hours_.get());
270     layout->StartRow(0, kColumnId);
271     layout->AddView(vertical_label_minutes_.get());
272     layout->AddPaddingRow(0, kTrayLabelItemVerticalPaddingVerticalAlignment);
273   }
274   Layout();
275 }
276 
SetBorder(TrayDate::ClockLayout clock_layout)277 void TimeView::SetBorder(TrayDate::ClockLayout clock_layout) {
278   if (clock_layout == TrayDate::HORIZONTAL_CLOCK)
279     set_border(views::Border::CreateEmptyBorder(
280         0, kTrayLabelItemHorizontalPaddingBottomAlignment,
281         0, kTrayLabelItemHorizontalPaddingBottomAlignment));
282   else
283     set_border(NULL);
284 }
285 
SetupLabels()286 void TimeView::SetupLabels() {
287   horizontal_label_.reset(CreateLabel());
288   SetupLabel(horizontal_label_.get());
289   vertical_label_hours_.reset(CreateLabel());
290   SetupLabel(vertical_label_hours_.get());
291   vertical_label_minutes_.reset(CreateLabel());
292   SetupLabel(vertical_label_minutes_.get());
293   vertical_label_minutes_->SetEnabledColor(kVerticalClockMinuteColor);
294   // Pull the minutes up closer to the hours by using a negative top border.
295   vertical_label_minutes_->set_border(
296       views::Border::CreateEmptyBorder(
297           kVerticalClockMinutesTopOffset, 0, 0, 0));
298 }
299 
SetupLabel(views::Label * label)300 void TimeView::SetupLabel(views::Label* label) {
301   label->set_owned_by_client();
302   SetupLabelForTray(label);
303   gfx::Font font = label->font();
304   label->SetFont(font.DeriveFont(0, font.GetStyle() & ~gfx::Font::BOLD));
305 }
306 
307 }  // namespace tray
308 }  // namespace internal
309 }  // namespace ash
310