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