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 26namespace ash { 27namespace tray { 28namespace { 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. 32const int kTimerSlopSeconds = 1; 33 34// Text color of the vertical clock minutes. 35const 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. 39const int kVerticalClockLeftPadding = 9; 40 41// Offset used to bring the minutes line closer to the hours line in the 42// vertical clock. 43const int kVerticalClockMinutesTopOffset = -4; 44 45base::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 54base::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 73views::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 82BaseDateTimeView::~BaseDateTimeView() { 83 timer_.Stop(); 84} 85 86void BaseDateTimeView::UpdateText() { 87 base::Time now = base::Time::Now(); 88 UpdateTextInternal(now); 89 SchedulePaint(); 90 SetTimer(now); 91} 92 93BaseDateTimeView::BaseDateTimeView() { 94 SetTimer(base::Time::Now()); 95} 96 97void 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 120void BaseDateTimeView::ChildPreferredSizeChanged(views::View* child) { 121 PreferredSizeChanged(); 122} 123 124void BaseDateTimeView::OnLocaleChanged() { 125 UpdateText(); 126} 127 128DateView::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 142DateView::~DateView() { 143} 144 145void 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 158void DateView::UpdateTimeFormat() { 159 hour_type_ = 160 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType(); 161 UpdateText(); 162} 163 164base::HourClockType DateView::GetHourTypeForTesting() const { 165 return hour_type_; 166} 167 168void 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 179bool 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 189void DateView::OnMouseEntered(const ui::MouseEvent& event) { 190 if (action_ == TrayDate::NONE) 191 return; 192 date_label_->SetEnabledColor(kHeaderTextColorHover); 193 SchedulePaint(); 194} 195 196void DateView::OnMouseExited(const ui::MouseEvent& event) { 197 if (action_ == TrayDate::NONE) 198 return; 199 date_label_->SetEnabledColor(kHeaderTextColorNormal); 200 SchedulePaint(); 201} 202 203/////////////////////////////////////////////////////////////////////////////// 204 205TimeView::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 214TimeView::~TimeView() { 215} 216 217void TimeView::UpdateTimeFormat() { 218 hour_type_ = 219 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType(); 220 UpdateText(); 221} 222 223base::HourClockType TimeView::GetHourTypeForTesting() const { 224 return hour_type_; 225} 226 227void 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 257bool TimeView::PerformAction(const ui::Event& event) { 258 return false; 259} 260 261bool TimeView::OnMousePressed(const ui::MouseEvent& event) { 262 // Let the event fall through. 263 return false; 264} 265 266void 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 293void 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 304void 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 317void 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