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