tray_display.cc 14.9 KB
Newer Older
1 2 3 4 5 6 7
// 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"
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 13 14
#include "ash/system/tray/system_tray.h"
#include "ash/system/tray/system_tray_delegate.h"
#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 20 21 22
#include "grit/ash_resources.h"
#include "grit/ash_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
23 24 25 26 27
#include "ui/message_center/message_center.h"
#include "ui/message_center/notification.h"
#include "ui/message_center/notification_delegate.h"
#include "ui/message_center/notification_list.h"
#include "ui/views/controls/image_view.h"
28 29 30
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"

31 32
using message_center::Notification;

33 34
namespace ash {
namespace internal {
35
namespace {
36

37
static const char kDisplayNotificationId[] = "chrome://settings/display";
38

39 40 41
DisplayManager* GetDisplayManager() {
  return Shell::GetInstance()->display_manager();
}
42

43 44 45 46
base::string16 GetDisplayName(int64 display_id) {
  return UTF8ToUTF16(GetDisplayManager()->GetDisplayNameForId(display_id));
}

47 48 49 50 51 52 53 54 55 56 57 58 59
base::string16 GetDisplaySize(int64 display_id) {
  DisplayManager* display_manager = GetDisplayManager();

  const gfx::Display* display = &display_manager->GetDisplayForId(display_id);
  if (display_manager->IsMirrored() &&
      display_manager->mirrored_display().id() == display_id) {
    display = &display_manager->mirrored_display();
  }

  DCHECK(display->is_valid());
  return UTF8ToUTF16(display->size().ToString());
}

60 61 62 63 64 65
// 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);

66
  base::string16 size_text = GetDisplaySize(display_id);
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
  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.
89
  if (display_manager->HasInternalDisplay() &&
90 91 92 93 94 95 96
      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) {
97
    int64 id = display_manager->GetDisplayAt(i).id();
98 99 100 101 102 103
    if (id == internal_id)
      continue;
    lines.push_back(GetDisplayInfoLine(id));
  }

  return JoinString(lines, '\n');
104 105 106 107
}

// Returns the name of the currently connected external display.
base::string16 GetExternalDisplayName() {
108
  DisplayManager* display_manager = GetDisplayManager();
109 110 111 112 113
  int64 external_id = display_manager->mirrored_display().id();

  if (external_id == gfx::Display::kInvalidDisplayID) {
    int64 internal_display_id = gfx::Display::InternalDisplayId();
    for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
114
      int64 id = display_manager->GetDisplayAt(i).id();
115 116 117 118 119 120 121
      if (id != internal_display_id) {
        external_id = id;
        break;
      }
    }
  }

122 123 124 125 126 127 128 129 130 131 132
  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()) {
133 134 135
    name = l10n_util::GetStringFUTF16(
        IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
        name, GetDisplaySize(external_id));
136 137
  } else if (display_info.overscan_insets_in_dip().empty() &&
             display_info.has_overscan()) {
138 139 140 141
    name = l10n_util::GetStringFUTF16(
        IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME,
        name, l10n_util::GetStringUTF16(
            IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN));
142 143
  }

144 145
  return name;
}
146

147 148 149 150 151 152
base::string16 GetTrayDisplayMessage() {
  DisplayManager* display_manager = GetDisplayManager();
  if (display_manager->GetNumDisplays() > 1) {
    if (GetDisplayManager()->HasInternalDisplay()) {
      return l10n_util::GetStringFUTF16(
          IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED, GetExternalDisplayName());
153
    }
154 155
    return l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED_NO_INTERNAL);
156 157
  }

158 159 160 161
  if (display_manager->IsMirrored()) {
    if (GetDisplayManager()->HasInternalDisplay()) {
      return l10n_util::GetStringFUTF16(
          IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING, GetExternalDisplayName());
162
    }
163 164
    return l10n_util::GetStringUTF16(
        IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING_NO_INTERNAL);
165 166
  }

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
  int64 first_id = display_manager->first_display_id();
  if (display_manager->HasInternalDisplay() &&
      !display_manager->IsInternalDisplayId(first_id)) {
    return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_DOCKED);
  }

  return base::string16();
}

void OpenSettings(user::LoginStatus login_status) {
  if (login_status == ash::user::LOGGED_IN_USER ||
      login_status == ash::user::LOGGED_IN_OWNER ||
      login_status == ash::user::LOGGED_IN_GUEST) {
    ash::Shell::GetInstance()->system_tray_delegate()->ShowDisplaySettings();
  }
}

void UpdateDisplayNotification(const base::string16& message) {
  // Always remove the notification to make sure the notification appears
  // as a popup in any situation.
  message_center::MessageCenter::Get()->RemoveNotification(
      kDisplayNotificationId, false /* by_user */);

  if (message.empty())
    return;

  ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
  scoped_ptr<Notification> notification(new Notification(
      message_center::NOTIFICATION_TYPE_SIMPLE,
      kDisplayNotificationId,
      message,
198
      base::string16(),  // body is intentionally empty, see crbug.com/265915
199 200 201 202
      bundle.GetImageNamed(IDR_AURA_UBER_TRAY_DISPLAY),
      base::string16(),  // display_source
      "",  // extension_id
      message_center::RichNotificationData(),
203 204 205 206
      new message_center::HandleNotificationClickedDelegate(
          base::Bind(&OpenSettings,
                     Shell::GetInstance()->system_tray_delegate()->
                     GetUserLoginStatus()))));
207 208 209
  message_center::MessageCenter::Get()->AddNotification(notification.Pass());
}

210 211
}  // namespace

212
class DisplayView : public ash::internal::ActionableView {
213 214
 public:
  explicit DisplayView(user::LoginStatus login_status)
215 216 217
      : login_status_(login_status) {
    SetLayoutManager(new views::BoxLayout(
        views::BoxLayout::kHorizontal,
218 219 220 221
        ash::kTrayPopupPaddingHorizontal, 0,
        ash::kTrayPopupPaddingBetweenItems));

    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 229 230 231

    label_ = new views::Label();
    label_->SetMultiLine(true);
    label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    AddChildView(label_);
232 233 234 235 236 237
    Update();
  }

  virtual ~DisplayView() {}

  void Update() {
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
    base::string16 message = GetTrayDisplayMessage();
    if (message.empty() && ShouldShowFirstDisplayInfo())
      message = GetDisplayInfoLine(GetDisplayManager()->first_display_id());
    SetVisible(!message.empty());
    label_->SetText(message);
  }

  views::Label* label() { return label_; }

  // Overridden from views::View.
  virtual bool GetTooltipText(const gfx::Point& p,
                              base::string16* tooltip) const OVERRIDE {
    base::string16 tray_message = GetTrayDisplayMessage();
    base::string16 display_message = GetAllDisplayInfo();
    if (tray_message.empty() && display_message.empty())
      return false;

    *tooltip = tray_message + ASCIIToUTF16("\n") + display_message;
    return true;
257 258
  }

259
 private:
260 261 262 263 264 265 266 267 268
  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();
  }

269 270
  // Overridden from ActionableView.
  virtual bool PerformAction(const ui::Event& event) OVERRIDE {
271
    OpenSettings(login_status_);
272 273 274
    return true;
  }

275 276 277
  virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE {
    int label_max_width = bounds().width() - kTrayPopupPaddingHorizontal * 2 -
        kTrayPopupPaddingBetweenItems - image_->GetPreferredSize().width();
278
    label_->SizeToFit(label_max_width);
279 280 281
    PreferredSizeChanged();
  }

282
  user::LoginStatus login_status_;
283
  views::ImageView* image_;
284
  views::Label* label_;
285 286 287 288

  DISALLOW_COPY_AND_ASSIGN(DisplayView);
};

289
class DisplayNotificationView : public TrayNotificationView {
290 291
 public:
  DisplayNotificationView(user::LoginStatus login_status,
292 293 294 295
                          TrayDisplay* tray_item,
                          const base::string16& message)
      : TrayNotificationView(tray_item, IDR_AURA_UBER_TRAY_DISPLAY),
        login_status_(login_status) {
296
    StartAutoCloseTimer(kTrayPopupAutoCloseDelayForTextInSeconds);
297
    Update(message);
298 299 300 301
  }

  virtual ~DisplayNotificationView() {}

302 303
  void Update(const base::string16& message) {
    if (message.empty()) {
304
      owner()->HideNotificationView();
305 306 307 308 309 310 311
    } else {
      views::Label* label = new views::Label(message);
      label->SetMultiLine(true);
      label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
      UpdateView(label);
      RestartAutoCloseTimer();
    }
312 313 314 315
  }

  // Overridden from TrayNotificationView:
  virtual void OnClickAction() OVERRIDE {
316
    OpenSettings(login_status_);
317 318 319
  }

 private:
320 321
  user::LoginStatus login_status_;

322 323 324
  DISALLOW_COPY_AND_ASSIGN(DisplayNotificationView);
};

325 326
TrayDisplay::TrayDisplay(SystemTray* system_tray)
    : 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 337 338 339 340
void TrayDisplay::UpdateDisplayInfo(TrayDisplay::DisplayInfoMap* old_info) {
  if (old_info)
    old_info->swap(display_info_);
  display_info_.clear();

341 342
  DisplayManager* display_manager = GetDisplayManager();
  for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
343
    int64 id = display_manager->GetDisplayAt(i).id();
344 345
    display_info_[id] = display_manager->GetDisplayInfo(id);
  }
346
}
347

348 349 350
bool TrayDisplay::GetDisplayMessageForNotification(
    base::string16* message,
    const TrayDisplay::DisplayInfoMap& old_info) {
351 352
  // Display is added or removed. Use the same message as the one in
  // the system tray.
353 354 355 356
  if (display_info_.size() != old_info.size()) {
    *message = GetTrayDisplayMessage();
    return true;
  }
357 358 359 360

  for (DisplayInfoMap::const_iterator iter = display_info_.begin();
       iter != display_info_.end(); ++iter) {
    DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first);
361 362 363 364 365 366 367
    // 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 = GetTrayDisplayMessage();
      return true;
    }
368 369

    if (iter->second.ui_scale() != old_iter->second.ui_scale()) {
370
      *message = l10n_util::GetStringFUTF16(
371 372
          IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
          GetDisplayName(iter->first),
373
          GetDisplaySize(iter->first));
374
      return true;
375 376
    }
    if (iter->second.rotation() != old_iter->second.rotation()) {
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
      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;
      }
392
      *message = l10n_util::GetStringFUTF16(
393 394 395
          IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED,
          GetDisplayName(iter->first),
          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 407 408 409
  default_ = new DisplayView(status);
  return default_;
}

410 411
void TrayDisplay::DestroyDefaultView() {
  default_ = NULL;
412 413
}

414
void TrayDisplay::OnDisplayConfigurationChanged() {
415 416 417
  DisplayInfoMap old_info;
  UpdateDisplayInfo(&old_info);

418 419 420 421 422
  if (!Shell::GetInstance()->system_tray_delegate()->
          ShouldShowDisplayNotification()) {
    return;
  }

423
  base::string16 message;
424
  if (GetDisplayMessageForNotification(&message, old_info))
425
    UpdateDisplayNotification(message);
426 427 428 429 430 431 432
}

base::string16 TrayDisplay::GetDefaultViewMessage() {
  if (!default_ || !default_->visible())
    return base::string16();

  return static_cast<DisplayView*>(default_)->label()->text();
433 434
}

435 436 437 438 439 440 441 442 443 444
base::string16 TrayDisplay::GetNotificationMessage() {
  message_center::NotificationList::Notifications notifications =
      message_center::MessageCenter::Get()->GetNotifications();
  for (message_center::NotificationList::Notifications::const_iterator iter =
           notifications.begin(); iter != notifications.end(); ++iter) {
    if ((*iter)->id() == kDisplayNotificationId)
      return (*iter)->title();
  }

  return base::string16();
445 446
}

447 448 449
void TrayDisplay::CloseNotificationForTest() {
  message_center::MessageCenter::Get()->RemoveNotification(
      kDisplayNotificationId, false);
450 451 452 453
}

}  // namespace internal
}  // namespace ash