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