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