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 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 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 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 84 BaseDateTimeView::~BaseDateTimeView() { 85 timer_.Stop(); 86 } 87 88 void BaseDateTimeView::UpdateText() { 89 base::Time now = base::Time::Now(); 90 UpdateTextInternal(now); 91 SchedulePaint(); 92 SetTimer(now); 93 } 94 95 BaseDateTimeView::BaseDateTimeView() { 96 SetTimer(base::Time::Now()); 97 } 98 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 122 void BaseDateTimeView::ChildPreferredSizeChanged(views::View* child) { 123 PreferredSizeChanged(); 124 } 125 126 void BaseDateTimeView::OnLocaleChanged() { 127 UpdateText(); 128 } 129 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 144 DateView::~DateView() { 145 } 146 147 void DateView::SetActionable(bool actionable) { 148 actionable_ = actionable; 149 SetFocusable(actionable_); 150 } 151 152 void DateView::UpdateTimeFormat() { 153 hour_type_ = 154 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType(); 155 UpdateText(); 156 } 157 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 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 177 void DateView::OnMouseEntered(const ui::MouseEvent& event) { 178 if (!actionable_) 179 return; 180 date_label_->SetEnabledColor(kHeaderTextColorHover); 181 SchedulePaint(); 182 } 183 184 void DateView::OnMouseExited(const ui::MouseEvent& event) { 185 if (!actionable_) 186 return; 187 date_label_->SetEnabledColor(kHeaderTextColorNormal); 188 SchedulePaint(); 189 } 190 191 /////////////////////////////////////////////////////////////////////////////// 192 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 202 TimeView::~TimeView() { 203 } 204 205 void TimeView::UpdateTimeFormat() { 206 hour_type_ = 207 ash::Shell::GetInstance()->system_tray_delegate()->GetHourClockType(); 208 UpdateText(); 209 } 210 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 241 bool TimeView::PerformAction(const ui::Event& event) { 242 return false; 243 } 244 245 bool TimeView::OnMousePressed(const ui::MouseEvent& event) { 246 // Let the event fall through. 247 return false; 248 } 249 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 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 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 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