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