tray_display.cc revision d3868032626d59662ff73b372b5d584c1d144c53
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/chromeos/tray_display.h" 6 7#include "ash/display/display_controller.h" 8#include "ash/display/display_manager.h" 9#include "ash/shell.h" 10#include "ash/system/tray/actionable_view.h" 11#include "ash/system/tray/fixed_sized_image_view.h" 12#include "ash/system/tray/system_tray.h" 13#include "ash/system/tray/system_tray_delegate.h" 14#include "ash/system/tray/tray_constants.h" 15#include "ash/system/tray/tray_notification_view.h" 16#include "base/strings/string_util.h" 17#include "base/strings/utf_string_conversions.h" 18#include "grit/ash_resources.h" 19#include "grit/ash_strings.h" 20#include "ui/base/l10n/l10n_util.h" 21#include "ui/base/resource/resource_bundle.h" 22#include "ui/message_center/message_center.h" 23#include "ui/message_center/notification.h" 24#include "ui/message_center/notification_delegate.h" 25#include "ui/message_center/notification_list.h" 26#include "ui/views/controls/image_view.h" 27#include "ui/views/controls/label.h" 28#include "ui/views/layout/box_layout.h" 29 30using message_center::Notification; 31 32namespace ash { 33namespace internal { 34namespace { 35 36static const char kDisplayNotificationId[] = "chrome://settings/display"; 37 38DisplayManager* GetDisplayManager() { 39 return Shell::GetInstance()->display_manager(); 40} 41 42base::string16 GetDisplayName(int64 display_id) { 43 return UTF8ToUTF16(GetDisplayManager()->GetDisplayNameForId(display_id)); 44} 45 46base::string16 GetDisplaySize(int64 display_id) { 47 DisplayManager* display_manager = GetDisplayManager(); 48 49 const gfx::Display* display = &display_manager->GetDisplayForId(display_id); 50 if (display_manager->IsMirrored() && 51 display_manager->mirrored_display().id() == display_id) { 52 display = &display_manager->mirrored_display(); 53 } 54 55 DCHECK(display->is_valid()); 56 return UTF8ToUTF16(display->size().ToString()); 57} 58 59// Returns 1-line information for the specified display, like 60// "InternalDisplay: 1280x750" 61base::string16 GetDisplayInfoLine(int64 display_id) { 62 const DisplayInfo& display_info = 63 GetDisplayManager()->GetDisplayInfo(display_id); 64 65 base::string16 size_text = GetDisplaySize(display_id); 66 base::string16 display_data; 67 if (display_info.has_overscan()) { 68 display_data = l10n_util::GetStringFUTF16( 69 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION, 70 size_text, 71 l10n_util::GetStringUTF16( 72 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN)); 73 } else { 74 display_data = size_text; 75 } 76 77 return l10n_util::GetStringFUTF16( 78 IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY, 79 GetDisplayName(display_id), 80 display_data); 81} 82 83base::string16 GetAllDisplayInfo() { 84 DisplayManager* display_manager = GetDisplayManager(); 85 std::vector<base::string16> lines; 86 int64 internal_id = gfx::Display::kInvalidDisplayID; 87 // Make sure to show the internal display first. 88 if (display_manager->HasInternalDisplay() && 89 display_manager->IsInternalDisplayId( 90 display_manager->first_display_id())) { 91 internal_id = display_manager->first_display_id(); 92 lines.push_back(GetDisplayInfoLine(internal_id)); 93 } 94 95 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { 96 int64 id = display_manager->GetDisplayAt(i).id(); 97 if (id == internal_id) 98 continue; 99 lines.push_back(GetDisplayInfoLine(id)); 100 } 101 102 return JoinString(lines, '\n'); 103} 104 105// Returns the name of the currently connected external display. 106base::string16 GetExternalDisplayName() { 107 DisplayManager* display_manager = GetDisplayManager(); 108 int64 external_id = display_manager->mirrored_display().id(); 109 110 if (external_id == gfx::Display::kInvalidDisplayID) { 111 int64 internal_display_id = gfx::Display::InternalDisplayId(); 112 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { 113 int64 id = display_manager->GetDisplayAt(i).id(); 114 if (id != internal_display_id) { 115 external_id = id; 116 break; 117 } 118 } 119 } 120 121 if (external_id == gfx::Display::kInvalidDisplayID) 122 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME); 123 124 // The external display name may have an annotation of "(width x height)" in 125 // case that the display is rotated or its resolution is changed. 126 base::string16 name = GetDisplayName(external_id); 127 const DisplayInfo& display_info = 128 display_manager->GetDisplayInfo(external_id); 129 if (display_info.rotation() != gfx::Display::ROTATE_0 || 130 display_info.ui_scale() != 1.0f || 131 !display_info.overscan_insets_in_dip().empty()) { 132 name = l10n_util::GetStringFUTF16( 133 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME, 134 name, GetDisplaySize(external_id)); 135 } else if (display_info.overscan_insets_in_dip().empty() && 136 display_info.has_overscan()) { 137 name = l10n_util::GetStringFUTF16( 138 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME, 139 name, l10n_util::GetStringUTF16( 140 IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN)); 141 } 142 143 return name; 144} 145 146base::string16 GetTrayDisplayMessage() { 147 DisplayManager* display_manager = GetDisplayManager(); 148 if (display_manager->GetNumDisplays() > 1) { 149 if (GetDisplayManager()->HasInternalDisplay()) { 150 return l10n_util::GetStringFUTF16( 151 IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetExternalDisplayName()); 152 } 153 return l10n_util::GetStringUTF16( 154 IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL); 155 } 156 157 if (display_manager->IsMirrored()) { 158 if (GetDisplayManager()->HasInternalDisplay()) { 159 return l10n_util::GetStringFUTF16( 160 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetExternalDisplayName()); 161 } 162 return l10n_util::GetStringUTF16( 163 IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL); 164 } 165 166 int64 first_id = display_manager->first_display_id(); 167 if (display_manager->HasInternalDisplay() && 168 !display_manager->IsInternalDisplayId(first_id)) { 169 return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED); 170 } 171 172 return base::string16(); 173} 174 175void OpenSettings(user::LoginStatus login_status) { 176 if (login_status == ash::user::LOGGED_IN_USER || 177 login_status == ash::user::LOGGED_IN_OWNER || 178 login_status == ash::user::LOGGED_IN_GUEST) { 179 ash::Shell::GetInstance()->system_tray_delegate()->ShowDisplaySettings(); 180 } 181} 182 183class DisplayNotificationDelegate 184 : public message_center::NotificationDelegate { 185 public: 186 DisplayNotificationDelegate(user::LoginStatus login_status) 187 : login_status_(login_status) {} 188 189 // message_center::NotificationDelegate overrides: 190 virtual void Display() OVERRIDE {} 191 virtual void Error() OVERRIDE {} 192 virtual void Close(bool by_user) OVERRIDE {} 193 virtual bool HasClickedListener() OVERRIDE { return true; } 194 virtual void Click() OVERRIDE { OpenSettings(login_status_); } 195 196 protected: 197 virtual ~DisplayNotificationDelegate() {} 198 199 private: 200 user::LoginStatus login_status_; 201 202 DISALLOW_COPY_AND_ASSIGN(DisplayNotificationDelegate); 203}; 204 205void UpdateDisplayNotification(const base::string16& message) { 206 // Always remove the notification to make sure the notification appears 207 // as a popup in any situation. 208 message_center::MessageCenter::Get()->RemoveNotification( 209 kDisplayNotificationId, false /* by_user */); 210 211 if (message.empty()) 212 return; 213 214 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 215 scoped_ptr<Notification> notification(new Notification( 216 message_center::NOTIFICATION_TYPE_SIMPLE, 217 kDisplayNotificationId, 218 message, 219 GetAllDisplayInfo(), 220 bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY), 221 base::string16(), // display_source 222 "", // extension_id 223 message_center::RichNotificationData(), 224 new DisplayNotificationDelegate( 225 Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus()))); 226 message_center::MessageCenter::Get()->AddNotification(notification.Pass()); 227} 228 229} // namespace 230 231class DisplayView : public ash::internal::ActionableView { 232 public: 233 explicit DisplayView(user::LoginStatus login_status) 234 : login_status_(login_status) { 235 SetLayoutManager(new views::BoxLayout( 236 views::BoxLayout::kHorizontal, 237 ash::kTrayPopupPaddingHorizontal, 0, 238 ash::kTrayPopupPaddingBetweenItems)); 239 240 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 241 image_ = 242 new ash::internal::FixedSizedImageView(0, ash::kTrayPopupItemHeight); 243 image_->SetImage( 244 bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY).ToImageSkia()); 245 AddChildView(image_); 246 247 label_ = new views::Label(); 248 label_->SetMultiLine(true); 249 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 250 AddChildView(label_); 251 Update(); 252 } 253 254 virtual ~DisplayView() {} 255 256 void Update() { 257 base::string16 message = GetTrayDisplayMessage(); 258 if (message.empty() && ShouldShowFirstDisplayInfo()) 259 message = GetDisplayInfoLine(GetDisplayManager()->first_display_id()); 260 SetVisible(!message.empty()); 261 label_->SetText(message); 262 } 263 264 views::Label* label() { return label_; } 265 266 // Overridden from views::View. 267 virtual bool GetTooltipText(const gfx::Point& p, 268 base::string16* tooltip) const OVERRIDE { 269 base::string16 tray_message = GetTrayDisplayMessage(); 270 base::string16 display_message = GetAllDisplayInfo(); 271 if (tray_message.empty() && display_message.empty()) 272 return false; 273 274 *tooltip = tray_message + ASCIIToUTF16("\n") + display_message; 275 return true; 276 } 277 278 private: 279 bool ShouldShowFirstDisplayInfo() const { 280 const DisplayInfo& display_info = GetDisplayManager()->GetDisplayInfo( 281 GetDisplayManager()->first_display_id()); 282 return display_info.rotation() != gfx::Display::ROTATE_0 || 283 display_info.ui_scale() != 1.0f || 284 !display_info.overscan_insets_in_dip().empty() || 285 display_info.has_overscan(); 286 } 287 288 // Overridden from ActionableView. 289 virtual bool PerformAction(const ui::Event& event) OVERRIDE { 290 OpenSettings(login_status_); 291 return true; 292 } 293 294 virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE { 295 int label_max_width = bounds().width() - kTrayPopupPaddingHorizontal * 2 - 296 kTrayPopupPaddingBetweenItems - image_->GetPreferredSize().width(); 297 label_->SizeToFit(label_max_width); 298 PreferredSizeChanged(); 299 } 300 301 user::LoginStatus login_status_; 302 views::ImageView* image_; 303 views::Label* label_; 304 305 DISALLOW_COPY_AND_ASSIGN(DisplayView); 306}; 307 308class DisplayNotificationView : public TrayNotificationView { 309 public: 310 DisplayNotificationView(user::LoginStatus login_status, 311 TrayDisplay* tray_item, 312 const base::string16& message) 313 : TrayNotificationView(tray_item, IDR_AURA_UBER_TRAY_DISPLAY), 314 login_status_(login_status) { 315 StartAutoCloseTimer(kTrayPopupAutoCloseDelayForTextInSeconds); 316 Update(message); 317 } 318 319 virtual ~DisplayNotificationView() {} 320 321 void Update(const base::string16& message) { 322 if (message.empty()) { 323 owner()->HideNotificationView(); 324 } else { 325 views::Label* label = new views::Label(message); 326 label->SetMultiLine(true); 327 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 328 UpdateView(label); 329 RestartAutoCloseTimer(); 330 } 331 } 332 333 // Overridden from TrayNotificationView: 334 virtual void OnClickAction() OVERRIDE { 335 OpenSettings(login_status_); 336 } 337 338 private: 339 user::LoginStatus login_status_; 340 341 DISALLOW_COPY_AND_ASSIGN(DisplayNotificationView); 342}; 343 344TrayDisplay::TrayDisplay(SystemTray* system_tray) 345 : SystemTrayItem(system_tray), 346 default_(NULL) { 347 Shell::GetInstance()->display_controller()->AddObserver(this); 348} 349 350TrayDisplay::~TrayDisplay() { 351 Shell::GetInstance()->display_controller()->RemoveObserver(this); 352} 353 354bool TrayDisplay::GetDisplayMessageForNotification(base::string16* message) { 355 DisplayManager* display_manager = GetDisplayManager(); 356 DisplayInfoMap old_info; 357 old_info.swap(display_info_); 358 for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { 359 int64 id = display_manager->GetDisplayAt(i).id(); 360 display_info_[id] = display_manager->GetDisplayInfo(id); 361 } 362 363 // Display is added or removed. Use the same message as the one in 364 // the system tray. 365 if (display_info_.size() != old_info.size()) { 366 *message = GetTrayDisplayMessage(); 367 return true; 368 } 369 370 for (DisplayInfoMap::const_iterator iter = display_info_.begin(); 371 iter != display_info_.end(); ++iter) { 372 DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first); 373 // The display's number is same but different displays. This happens 374 // for the transition between docked mode and mirrored display. Falls back 375 // to GetTrayDisplayMessage(). 376 if (old_iter == old_info.end()) { 377 *message = GetTrayDisplayMessage(); 378 return true; 379 } 380 381 if (iter->second.ui_scale() != old_iter->second.ui_scale()) { 382 *message = l10n_util::GetStringFUTF16( 383 IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, 384 GetDisplayName(iter->first), 385 GetDisplaySize(iter->first)); 386 return true; 387 } 388 if (iter->second.rotation() != old_iter->second.rotation()) { 389 *message = l10n_util::GetStringFUTF16( 390 IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetDisplayName(iter->first)); 391 return true; 392 } 393 } 394 395 // Found nothing special 396 return false; 397} 398 399views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) { 400 DCHECK(default_ == NULL); 401 default_ = new DisplayView(status); 402 return default_; 403} 404 405void TrayDisplay::DestroyDefaultView() { 406 default_ = NULL; 407} 408 409void TrayDisplay::OnDisplayConfigurationChanged() { 410 if (!Shell::GetInstance()->system_tray_delegate()-> 411 ShouldShowDisplayNotification()) { 412 return; 413 } 414 415 base::string16 message; 416 if (GetDisplayMessageForNotification(&message)) 417 UpdateDisplayNotification(message); 418} 419 420base::string16 TrayDisplay::GetDefaultViewMessage() { 421 if (!default_ || !default_->visible()) 422 return base::string16(); 423 424 return static_cast<DisplayView*>(default_)->label()->text(); 425} 426 427base::string16 TrayDisplay::GetNotificationMessage() { 428 message_center::NotificationList::Notifications notifications = 429 message_center::MessageCenter::Get()->GetNotifications(); 430 for (message_center::NotificationList::Notifications::const_iterator iter = 431 notifications.begin(); iter != notifications.end(); ++iter) { 432 if ((*iter)->id() == kDisplayNotificationId) 433 return (*iter)->title(); 434 } 435 436 return base::string16(); 437} 438 439void TrayDisplay::CloseNotificationForTest() { 440 message_center::MessageCenter::Get()->RemoveNotification( 441 kDisplayNotificationId, false); 442} 443 444} // namespace internal 445} // namespace ash 446