| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/chromeos/tray_display.h" |
| |
| #include "ash/display/display_controller.h" |
| #include "ash/display/display_manager.h" |
| #include "ash/shell.h" |
| #include "ash/system/system_notifier.h" |
| #include "ash/system/tray/actionable_view.h" |
| #include "ash/system/tray/fixed_sized_image_view.h" |
| #include "ash/system/tray/system_tray.h" |
| #include "ash/system/tray/system_tray_delegate.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/tray/tray_notification_view.h" |
| #include "base/bind.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "grit/ash_resources.h" |
| #include "grit/ash_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/notification.h" |
| #include "ui/message_center/notification_delegate.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/layout/box_layout.h" |
| |
| using message_center::Notification; |
| |
| namespace ash { |
| namespace internal { |
| namespace { |
| |
| DisplayManager* GetDisplayManager() { |
| return Shell::GetInstance()->display_manager(); |
| } |
| |
| base::string16 GetDisplayName(int64 display_id) { |
| return UTF8ToUTF16(GetDisplayManager()->GetDisplayNameForId(display_id)); |
| } |
| |
| base::string16 GetDisplaySize(int64 display_id) { |
| DisplayManager* display_manager = GetDisplayManager(); |
| |
| const gfx::Display* display = &display_manager->GetDisplayForId(display_id); |
| |
| // We don't show display size for mirrored display. Fallback |
| // to empty string if this happens on release build. |
| bool mirrored_display = display_manager->mirrored_display_id() == display_id; |
| DCHECK(!mirrored_display); |
| if (mirrored_display) |
| return base::string16(); |
| |
| DCHECK(display->is_valid()); |
| return UTF8ToUTF16(display->size().ToString()); |
| } |
| |
| // Returns 1-line information for the specified display, like |
| // "InternalDisplay: 1280x750" |
| base::string16 GetDisplayInfoLine(int64 display_id) { |
| const DisplayInfo& display_info = |
| GetDisplayManager()->GetDisplayInfo(display_id); |
| if (GetDisplayManager()->mirrored_display_id() == display_id) |
| return GetDisplayName(display_id); |
| |
| base::string16 size_text = GetDisplaySize(display_id); |
| base::string16 display_data; |
| if (display_info.has_overscan()) { |
| display_data = l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION, |
| size_text, |
| l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN)); |
| } else { |
| display_data = size_text; |
| } |
| |
| return l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_SINGLE_DISPLAY, |
| GetDisplayName(display_id), |
| display_data); |
| } |
| |
| base::string16 GetAllDisplayInfo() { |
| DisplayManager* display_manager = GetDisplayManager(); |
| std::vector<base::string16> lines; |
| int64 internal_id = gfx::Display::kInvalidDisplayID; |
| // Make sure to show the internal display first. |
| if (display_manager->HasInternalDisplay() && |
| display_manager->IsInternalDisplayId( |
| display_manager->first_display_id())) { |
| internal_id = display_manager->first_display_id(); |
| lines.push_back(GetDisplayInfoLine(internal_id)); |
| } |
| |
| for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { |
| int64 id = display_manager->GetDisplayAt(i).id(); |
| if (id == internal_id) |
| continue; |
| lines.push_back(GetDisplayInfoLine(id)); |
| } |
| |
| return JoinString(lines, '\n'); |
| } |
| |
| // Returns the name of the currently connected external display. |
| // This should not be used when the external display is used for |
| // mirroring. |
| base::string16 GetExternalDisplayName() { |
| DisplayManager* display_manager = GetDisplayManager(); |
| DCHECK(!display_manager->IsMirrored()); |
| |
| int64 external_id = gfx::Display::kInvalidDisplayID; |
| for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { |
| int64 id = display_manager->GetDisplayAt(i).id(); |
| if (id != gfx::Display::InternalDisplayId()) { |
| external_id = id; |
| break; |
| } |
| } |
| |
| if (external_id == gfx::Display::kInvalidDisplayID) |
| return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_UNKNOWN_DISPLAY_NAME); |
| |
| // The external display name may have an annotation of "(width x height)" in |
| // case that the display is rotated or its resolution is changed. |
| base::string16 name = GetDisplayName(external_id); |
| const DisplayInfo& display_info = |
| display_manager->GetDisplayInfo(external_id); |
| if (display_info.rotation() != gfx::Display::ROTATE_0 || |
| display_info.ui_scale() != 1.0f || |
| !display_info.overscan_insets_in_dip().empty()) { |
| name = l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME, |
| name, GetDisplaySize(external_id)); |
| } else if (display_info.overscan_insets_in_dip().empty() && |
| display_info.has_overscan()) { |
| name = l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME, |
| name, l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN)); |
| } |
| |
| return name; |
| } |
| |
| base::string16 GetTrayDisplayMessage(base::string16* additional_message_out) { |
| DisplayManager* display_manager = GetDisplayManager(); |
| if (display_manager->GetNumDisplays() > 1) { |
| if (GetDisplayManager()->HasInternalDisplay()) { |
| return l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetExternalDisplayName()); |
| } |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL); |
| } |
| |
| if (display_manager->IsMirrored()) { |
| if (GetDisplayManager()->HasInternalDisplay()) { |
| return l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, |
| GetDisplayName(display_manager->mirrored_display_id())); |
| } |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL); |
| } |
| |
| int64 primary_id = Shell::GetScreen()->GetPrimaryDisplay().id(); |
| if (display_manager->HasInternalDisplay() && |
| !display_manager->IsInternalDisplayId(primary_id)) { |
| if (additional_message_out) { |
| *additional_message_out = l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED_DESCRIPTION); |
| } |
| return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED); |
| } |
| |
| return base::string16(); |
| } |
| |
| void OpenSettings() { |
| user::LoginStatus login_status = |
| Shell::GetInstance()->system_tray_delegate()->GetUserLoginStatus(); |
| if (login_status == user::LOGGED_IN_USER || |
| login_status == user::LOGGED_IN_OWNER || |
| login_status == user::LOGGED_IN_GUEST) { |
| Shell::GetInstance()->system_tray_delegate()->ShowDisplaySettings(); |
| } |
| } |
| |
| } // namespace |
| |
| const char TrayDisplay::kNotificationId[] = "chrome://settings/display"; |
| |
| class DisplayView : public internal::ActionableView { |
| public: |
| explicit DisplayView() { |
| SetLayoutManager(new views::BoxLayout( |
| views::BoxLayout::kHorizontal, |
| kTrayPopupPaddingHorizontal, 0, |
| kTrayPopupPaddingBetweenItems)); |
| |
| ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| image_ = new internal::FixedSizedImageView(0, kTrayPopupItemHeight); |
| image_->SetImage( |
| bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY).ToImageSkia()); |
| AddChildView(image_); |
| |
| label_ = new views::Label(); |
| label_->SetMultiLine(true); |
| label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| AddChildView(label_); |
| Update(); |
| } |
| |
| virtual ~DisplayView() {} |
| |
| void Update() { |
| base::string16 message = GetTrayDisplayMessage(NULL); |
| if (message.empty() && ShouldShowFirstDisplayInfo()) |
| message = GetDisplayInfoLine(GetDisplayManager()->first_display_id()); |
| SetVisible(!message.empty()); |
| label_->SetText(message); |
| Layout(); |
| } |
| |
| const views::Label* label() const { return label_; } |
| |
| // Overridden from views::View. |
| virtual bool GetTooltipText(const gfx::Point& p, |
| base::string16* tooltip) const OVERRIDE { |
| base::string16 tray_message = GetTrayDisplayMessage(NULL); |
| base::string16 display_message = GetAllDisplayInfo(); |
| if (tray_message.empty() && display_message.empty()) |
| return false; |
| |
| *tooltip = tray_message + ASCIIToUTF16("\n") + display_message; |
| return true; |
| } |
| |
| private: |
| bool ShouldShowFirstDisplayInfo() const { |
| const DisplayInfo& display_info = GetDisplayManager()->GetDisplayInfo( |
| GetDisplayManager()->first_display_id()); |
| return display_info.rotation() != gfx::Display::ROTATE_0 || |
| display_info.ui_scale() != 1.0f || |
| !display_info.overscan_insets_in_dip().empty() || |
| display_info.has_overscan(); |
| } |
| |
| // Overridden from ActionableView. |
| virtual bool PerformAction(const ui::Event& event) OVERRIDE { |
| OpenSettings(); |
| return true; |
| } |
| |
| virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE { |
| int label_max_width = bounds().width() - kTrayPopupPaddingHorizontal * 2 - |
| kTrayPopupPaddingBetweenItems - image_->GetPreferredSize().width(); |
| label_->SizeToFit(label_max_width); |
| PreferredSizeChanged(); |
| } |
| |
| views::ImageView* image_; |
| views::Label* label_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DisplayView); |
| }; |
| |
| TrayDisplay::TrayDisplay(SystemTray* system_tray) |
| : SystemTrayItem(system_tray), |
| default_(NULL) { |
| Shell::GetInstance()->display_controller()->AddObserver(this); |
| UpdateDisplayInfo(NULL); |
| } |
| |
| TrayDisplay::~TrayDisplay() { |
| Shell::GetInstance()->display_controller()->RemoveObserver(this); |
| } |
| |
| void TrayDisplay::UpdateDisplayInfo(TrayDisplay::DisplayInfoMap* old_info) { |
| if (old_info) |
| old_info->swap(display_info_); |
| display_info_.clear(); |
| |
| DisplayManager* display_manager = GetDisplayManager(); |
| for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) { |
| int64 id = display_manager->GetDisplayAt(i).id(); |
| display_info_[id] = display_manager->GetDisplayInfo(id); |
| } |
| } |
| |
| bool TrayDisplay::GetDisplayMessageForNotification( |
| const TrayDisplay::DisplayInfoMap& old_info, |
| base::string16* message_out, |
| base::string16* additional_message_out) { |
| // Display is added or removed. Use the same message as the one in |
| // the system tray. |
| if (display_info_.size() != old_info.size()) { |
| *message_out = GetTrayDisplayMessage(additional_message_out); |
| return true; |
| } |
| |
| for (DisplayInfoMap::const_iterator iter = display_info_.begin(); |
| iter != display_info_.end(); ++iter) { |
| DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first); |
| // The display's number is same but different displays. This happens |
| // for the transition between docked mode and mirrored display. Falls back |
| // to GetTrayDisplayMessage(). |
| if (old_iter == old_info.end()) { |
| *message_out = GetTrayDisplayMessage(additional_message_out); |
| return true; |
| } |
| |
| if (iter->second.ui_scale() != old_iter->second.ui_scale()) { |
| *message_out = l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED, |
| GetDisplayName(iter->first), |
| GetDisplaySize(iter->first)); |
| return true; |
| } |
| if (iter->second.rotation() != old_iter->second.rotation()) { |
| int rotation_text_id = 0; |
| switch (iter->second.rotation()) { |
| case gfx::Display::ROTATE_0: |
| rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION; |
| break; |
| case gfx::Display::ROTATE_90: |
| rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90; |
| break; |
| case gfx::Display::ROTATE_180: |
| rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180; |
| break; |
| case gfx::Display::ROTATE_270: |
| rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270; |
| break; |
| } |
| *message_out = l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, |
| GetDisplayName(iter->first), |
| l10n_util::GetStringUTF16(rotation_text_id)); |
| return true; |
| } |
| } |
| |
| // Found nothing special |
| return false; |
| } |
| |
| void TrayDisplay::CreateOrUpdateNotification( |
| const base::string16& message, |
| const base::string16& additional_message) { |
| // Always remove the notification to make sure the notification appears |
| // as a popup in any situation. |
| message_center::MessageCenter::Get()->RemoveNotification( |
| kNotificationId, false /* by_user */); |
| |
| if (message.empty()) |
| return; |
| |
| ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| scoped_ptr<Notification> notification(new Notification( |
| message_center::NOTIFICATION_TYPE_SIMPLE, |
| kNotificationId, |
| message, |
| additional_message, |
| bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY), |
| base::string16(), // display_source |
| message_center::NotifierId(system_notifier::NOTIFIER_DISPLAY), |
| message_center::RichNotificationData(), |
| new message_center::HandleNotificationClickedDelegate( |
| base::Bind(&OpenSettings)))); |
| message_center::MessageCenter::Get()->AddNotification(notification.Pass()); |
| } |
| |
| views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) { |
| DCHECK(default_ == NULL); |
| default_ = new DisplayView(); |
| return default_; |
| } |
| |
| void TrayDisplay::DestroyDefaultView() { |
| default_ = NULL; |
| } |
| |
| void TrayDisplay::OnDisplayConfigurationChanged() { |
| DisplayInfoMap old_info; |
| UpdateDisplayInfo(&old_info); |
| |
| if (default_) |
| default_->Update(); |
| |
| if (!Shell::GetInstance()->system_tray_delegate()-> |
| ShouldShowDisplayNotification()) { |
| return; |
| } |
| |
| base::string16 message; |
| base::string16 additional_message; |
| if (GetDisplayMessageForNotification(old_info, &message, &additional_message)) |
| CreateOrUpdateNotification(message, additional_message); |
| } |
| |
| base::string16 TrayDisplay::GetDefaultViewMessage() const { |
| if (!default_ || !default_->visible()) |
| return base::string16(); |
| |
| return static_cast<DisplayView*>(default_)->label()->text(); |
| } |
| |
| } // namespace internal |
| } // namespace ash |