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