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