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