tray_display.cc revision ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16
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
31using message_center::Notification;
32
33namespace ash {
34namespace internal {
35namespace {
36
37static const char kDisplayNotificationId[] = "chrome://settings/display";
38
39DisplayManager* GetDisplayManager() {
40  return Shell::GetInstance()->display_manager();
41}
42
43base::string16 GetDisplayName(int64 display_id) {
44  return UTF8ToUTF16(GetDisplayManager()->GetDisplayNameForId(display_id));
45}
46
47base::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"
62base::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
84base::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.
107base::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
147base::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
176void 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
184void 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
212class 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
289class 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
325TrayDisplay::TrayDisplay(SystemTray* system_tray)
326    : SystemTrayItem(system_tray),
327      default_(NULL) {
328  Shell::GetInstance()->display_controller()->AddObserver(this);
329}
330
331TrayDisplay::~TrayDisplay() {
332  Shell::GetInstance()->display_controller()->RemoveObserver(this);
333}
334
335bool TrayDisplay::GetDisplayMessageForNotification(base::string16* message) {
336  DisplayManager* display_manager = GetDisplayManager();
337  DisplayInfoMap old_info;
338  old_info.swap(display_info_);
339  for (size_t i = 0; i < display_manager->GetNumDisplays(); ++i) {
340    int64 id = display_manager->GetDisplayAt(i).id();
341    display_info_[id] = display_manager->GetDisplayInfo(id);
342  }
343
344  // Display is added or removed. Use the same message as the one in
345  // the system tray.
346  if (display_info_.size() != old_info.size()) {
347    *message = GetTrayDisplayMessage();
348    return true;
349  }
350
351  for (DisplayInfoMap::const_iterator iter = display_info_.begin();
352       iter != display_info_.end(); ++iter) {
353    DisplayInfoMap::const_iterator old_iter = old_info.find(iter->first);
354    // The display's number is same but different displays. This happens
355    // for the transition between docked mode and mirrored display. Falls back
356    // to GetTrayDisplayMessage().
357    if (old_iter == old_info.end()) {
358      *message = GetTrayDisplayMessage();
359      return true;
360    }
361
362    if (iter->second.ui_scale() != old_iter->second.ui_scale()) {
363      *message = l10n_util::GetStringFUTF16(
364          IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED,
365          GetDisplayName(iter->first),
366          GetDisplaySize(iter->first));
367      return true;
368    }
369    if (iter->second.rotation() != old_iter->second.rotation()) {
370      int rotation_text_id = 0;
371      switch (iter->second.rotation()) {
372        case gfx::Display::ROTATE_0:
373          rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION;
374          break;
375        case gfx::Display::ROTATE_90:
376          rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90;
377          break;
378        case gfx::Display::ROTATE_180:
379          rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180;
380          break;
381        case gfx::Display::ROTATE_270:
382          rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270;
383          break;
384      }
385      *message = l10n_util::GetStringFUTF16(
386          IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED,
387          GetDisplayName(iter->first),
388          l10n_util::GetStringUTF16(rotation_text_id));
389      return true;
390    }
391  }
392
393  // Found nothing special
394  return false;
395}
396
397views::View* TrayDisplay::CreateDefaultView(user::LoginStatus status) {
398  DCHECK(default_ == NULL);
399  default_ = new DisplayView(status);
400  return default_;
401}
402
403void TrayDisplay::DestroyDefaultView() {
404  default_ = NULL;
405}
406
407void TrayDisplay::OnDisplayConfigurationChanged() {
408  if (!Shell::GetInstance()->system_tray_delegate()->
409          ShouldShowDisplayNotification()) {
410    return;
411  }
412
413  base::string16 message;
414  if (GetDisplayMessageForNotification(&message))
415    UpdateDisplayNotification(message);
416}
417
418base::string16 TrayDisplay::GetDefaultViewMessage() {
419  if (!default_ || !default_->visible())
420    return base::string16();
421
422  return static_cast<DisplayView*>(default_)->label()->text();
423}
424
425base::string16 TrayDisplay::GetNotificationMessage() {
426  message_center::NotificationList::Notifications notifications =
427      message_center::MessageCenter::Get()->GetNotifications();
428  for (message_center::NotificationList::Notifications::const_iterator iter =
429           notifications.begin(); iter != notifications.end(); ++iter) {
430    if ((*iter)->id() == kDisplayNotificationId)
431      return (*iter)->title();
432  }
433
434  return base::string16();
435}
436
437void TrayDisplay::CloseNotificationForTest() {
438  message_center::MessageCenter::Get()->RemoveNotification(
439      kDisplayNotificationId, false);
440}
441
442}  // namespace internal
443}  // namespace ash
444