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