web_notification_tray.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
1// Copyright 2013 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 "chrome/browser/ui/views/message_center/web_notification_tray.h"
6
7#include "base/i18n/number_formatting.h"
8#include "base/strings/string16.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/browser/browser_process.h"
11#include "chrome/browser/status_icons/status_icon.h"
12#include "chrome/browser/status_icons/status_tray.h"
13#include "content/public/browser/user_metrics.h"
14#include "grit/chromium_strings.h"
15#include "grit/theme_resources.h"
16#include "grit/ui_strings.h"
17#include "ui/base/l10n/l10n_util.h"
18#include "ui/base/resource/resource_bundle.h"
19#include "ui/gfx/canvas.h"
20#include "ui/gfx/image/image_skia_operations.h"
21#include "ui/gfx/rect.h"
22#include "ui/gfx/screen.h"
23#include "ui/gfx/size.h"
24#include "ui/message_center/message_center_tray.h"
25#include "ui/message_center/message_center_tray_delegate.h"
26#include "ui/message_center/views/message_popup_collection.h"
27#include "ui/views/widget/widget.h"
28
29namespace {
30
31// Tray constants
32const int kScreenEdgePadding = 2;
33
34const int kSystemTrayWidth = 16;
35const int kSystemTrayHeight = 16;
36const int kNumberOfSystemTraySprites = 10;
37
38gfx::ImageSkia GetIcon(int unread_count) {
39  bool has_unread = unread_count > 0;
40  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
41  if (!has_unread)
42    return *rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_EMPTY);
43
44  return *rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_ATTENTION);
45}
46
47}  // namespace
48
49using content::UserMetricsAction;
50
51namespace message_center {
52
53namespace internal {
54
55// Gets the position of the taskbar from the work area bounds. Returns
56// ALIGNMENT_NONE if position cannot be found.
57Alignment GetTaskbarAlignment() {
58  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
59  // TODO(dewittj): It's possible GetPrimaryDisplay is wrong.
60  gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds();
61  gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
62  work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
63
64  // Comparing the work area to the screen bounds gives us the location of the
65  // taskbar.  If the work area is exactly the same as the screen bounds,
66  // we are unable to locate the taskbar so we say we don't know it's alignment.
67  if (work_area.height() < screen_bounds.height()) {
68    if (work_area.y() > screen_bounds.y())
69      return ALIGNMENT_TOP;
70    return ALIGNMENT_BOTTOM;
71  }
72  if (work_area.width() < screen_bounds.width()) {
73    if (work_area.x() > screen_bounds.x())
74      return ALIGNMENT_LEFT;
75    return ALIGNMENT_RIGHT;
76  }
77
78  return ALIGNMENT_NONE;
79}
80
81gfx::Point GetClosestCorner(const gfx::Rect& rect, const gfx::Point& query) {
82  gfx::Point center_point = rect.CenterPoint();
83  gfx::Point rv;
84
85  if (query.x() > center_point.x())
86    rv.set_x(rect.right());
87  else
88    rv.set_x(rect.x());
89
90  if (query.y() > center_point.y())
91    rv.set_y(rect.bottom());
92  else
93    rv.set_y(rect.y());
94
95  return rv;
96}
97
98// Gets the corner of the screen where the message center should pop up.
99Alignment GetAnchorAlignment(const gfx::Rect& work_area, gfx::Point corner) {
100  gfx::Point center = work_area.CenterPoint();
101
102  Alignment anchor_alignment =
103      center.y() > corner.y() ? ALIGNMENT_TOP : ALIGNMENT_BOTTOM;
104  anchor_alignment =
105      (Alignment)(anchor_alignment |
106                  (center.x() > corner.x() ? ALIGNMENT_LEFT : ALIGNMENT_RIGHT));
107
108  return anchor_alignment;
109}
110
111}  // namespace internal
112
113MessageCenterTrayDelegate* CreateMessageCenterTray() {
114  return new WebNotificationTray();
115}
116
117WebNotificationTray::WebNotificationTray()
118    : message_center_delegate_(NULL),
119      status_icon_(NULL),
120      message_center_visible_(false),
121      should_update_tray_content_(true) {
122  message_center_tray_.reset(
123      new MessageCenterTray(this, g_browser_process->message_center()));
124  UpdateStatusIcon();
125}
126
127WebNotificationTray::~WebNotificationTray() {
128  // Reset this early so that delegated events during destruction don't cause
129  // problems.
130  message_center_tray_.reset();
131  DestroyStatusIcon();
132}
133
134message_center::MessageCenter* WebNotificationTray::message_center() {
135  return message_center_tray_->message_center();
136}
137
138bool WebNotificationTray::ShowPopups() {
139  popup_collection_.reset(new message_center::MessagePopupCollection(
140      NULL, message_center(), message_center_tray_.get()));
141  return true;
142}
143
144void WebNotificationTray::HidePopups() { popup_collection_.reset(); }
145
146bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) {
147  content::RecordAction(UserMetricsAction("Notifications.ShowMessageCenter"));
148
149  // Message center delegate will be set to NULL when the message center
150  // widget's Close method is called so we don't need to worry about
151  // use-after-free issues.
152  message_center_delegate_ = new MessageCenterWidgetDelegate(
153      this,
154      message_center_tray_.get(),
155      show_settings,  // settings initally invisible
156      GetPositionInfo());
157
158  return true;
159}
160
161bool WebNotificationTray::ShowMessageCenter() {
162  return ShowMessageCenterInternal(/*show_settings =*/false);
163}
164
165void WebNotificationTray::HideMessageCenter() {
166  if (message_center_delegate_) {
167    views::Widget* widget = message_center_delegate_->GetWidget();
168    if (widget)
169      widget->Close();
170  }
171}
172
173bool WebNotificationTray::ShowNotifierSettings() {
174  if (message_center_delegate_) {
175    message_center_delegate_->SetSettingsVisible(true);
176    return true;
177  }
178  return ShowMessageCenterInternal(/*show_settings =*/true);
179}
180
181void WebNotificationTray::OnMessageCenterTrayChanged() {
182  // See the comments in ash/system/web_notification/web_notification_tray.cc
183  // for why PostTask.
184  should_update_tray_content_ = true;
185  base::MessageLoop::current()->PostTask(
186      FROM_HERE,
187      base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr()));
188}
189
190void WebNotificationTray::OnStatusIconClicked() {
191  // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura.
192  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
193  mouse_click_point_ = screen->GetCursorScreenPoint();
194  message_center_tray_->ToggleMessageCenterBubble();
195}
196
197void WebNotificationTray::UpdateStatusIcon() {
198  if (!should_update_tray_content_)
199    return;
200  should_update_tray_content_ = false;
201
202  int total_notifications = message_center()->NotificationCount();
203  if (total_notifications == 0) {
204    DestroyStatusIcon();
205    return;
206  }
207
208  int unread_notifications = message_center()->UnreadNotificationCount();
209  StatusIcon* status_icon = GetStatusIcon();
210  if (!status_icon)
211    return;
212
213  status_icon->SetImage(GetIcon(unread_notifications));
214
215  string16 product_name(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
216  if (unread_notifications > 0) {
217    string16 str_unread_count = base::FormatNumber(unread_notifications);
218    status_icon->SetToolTip(l10n_util::GetStringFUTF16(
219        IDS_MESSAGE_CENTER_TOOLTIP_UNREAD, product_name, str_unread_count));
220  } else {
221    status_icon->SetToolTip(
222        l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP, product_name));
223  }
224}
225
226void WebNotificationTray::SendHideMessageCenter() {
227  message_center_tray_->HideMessageCenterBubble();
228}
229
230void WebNotificationTray::MarkMessageCenterHidden() {
231  if (message_center_delegate_) {
232    message_center_tray_->MarkMessageCenterHidden();
233    message_center_delegate_ = NULL;
234  }
235}
236
237PositionInfo WebNotificationTray::GetPositionInfo() {
238  PositionInfo pos_info;
239
240  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
241  gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
242  work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
243
244  gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_);
245
246  pos_info.taskbar_alignment = internal::GetTaskbarAlignment();
247
248  // We assume the taskbar is either at the top or at the bottom if we are not
249  // able to find it.
250  if (pos_info.taskbar_alignment == ALIGNMENT_NONE) {
251    if (mouse_click_point_.y() > corner.y())
252      pos_info.taskbar_alignment = ALIGNMENT_TOP;
253    else
254      pos_info.taskbar_alignment = ALIGNMENT_BOTTOM;
255  }
256
257  pos_info.message_center_alignment =
258      internal::GetAnchorAlignment(work_area, corner);
259
260  pos_info.inital_anchor_point = corner;
261  pos_info.max_height = work_area.height();
262
263  if (work_area.Contains(mouse_click_point_)) {
264    pos_info.max_height -= std::abs(mouse_click_point_.y() - corner.y());
265
266    // Message center is in the work area. So position it few pixels above the
267    // mouse click point if alignemnt is towards bottom and few pixels below if
268    // alignment is towards top.
269    pos_info.inital_anchor_point
270        .set_y(mouse_click_point_.y() +
271               (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -5 : 5));
272  }
273  return pos_info;
274}
275
276StatusIcon* WebNotificationTray::GetStatusIcon() {
277  if (status_icon_)
278    return status_icon_;
279
280  StatusTray* status_tray = g_browser_process->status_tray();
281  if (!status_tray)
282    return NULL;
283
284  StatusIcon* status_icon =
285      status_tray->CreateStatusIcon(StatusTray::NOTIFICATION_TRAY_ICON);
286  if (!status_icon)
287    return NULL;
288
289  status_icon_ = status_icon;
290  status_icon_->AddObserver(this);
291  AddQuietModeMenu(status_icon_);
292
293  return status_icon_;
294}
295
296void WebNotificationTray::DestroyStatusIcon() {
297  if (!status_icon_)
298    return;
299
300  status_icon_->RemoveObserver(this);
301  StatusTray* status_tray = g_browser_process->status_tray();
302  if (status_tray)
303    status_tray->RemoveStatusIcon(status_icon_);
304  status_icon_ = NULL;
305}
306
307void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) {
308  DCHECK(status_icon);
309  status_icon->SetContextMenu(message_center_tray_->CreateQuietModeMenu());
310}
311
312MessageCenterWidgetDelegate*
313WebNotificationTray::GetMessageCenterWidgetDelegateForTest() {
314  return message_center_delegate_;
315}
316
317}  // namespace message_center
318