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