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