web_notification_tray.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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_icon_menu_model.h"
13#include "chrome/browser/status_icons/status_tray.h"
14#include "content/public/browser/notification_service.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
35// Number of pixels the message center is offset from the mouse.
36const int kMouseOffset = 5;
37
38// Menu commands
39const int kToggleQuietMode = 0;
40const int kEnableQuietModeHour = 1;
41const int kEnableQuietModeDay = 2;
42
43gfx::ImageSkia* GetIcon(int unread_count, bool is_quiet_mode) {
44  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
45  int resource_id = IDR_NOTIFICATION_TRAY_EMPTY;
46
47  if (unread_count) {
48    if (is_quiet_mode)
49      resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_ATTENTION;
50    else
51      resource_id = IDR_NOTIFICATION_TRAY_ATTENTION;
52  } else if (is_quiet_mode) {
53    resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_EMPTY;
54  }
55
56  return rb.GetImageSkiaNamed(resource_id);
57}
58
59}  // namespace
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      status_icon_menu_(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  last_quiet_mode_state_ = message_center()->IsQuietMode();
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() {
156  DCHECK(popup_collection_.get());
157
158  popup_collection_->MarkAllPopupsShown();
159  popup_collection_.reset();
160}
161
162bool WebNotificationTray::ShowMessageCenter() {
163  message_center_delegate_ =
164      new MessageCenterWidgetDelegate(this,
165                                      message_center_tray_.get(),
166                                      false,  // settings initally invisible
167                                      GetPositionInfo());
168
169  return true;
170}
171
172void WebNotificationTray::HideMessageCenter() {
173  if (message_center_delegate_) {
174    views::Widget* widget = message_center_delegate_->GetWidget();
175    if (widget)
176      widget->Close();
177  }
178}
179
180bool WebNotificationTray::ShowNotifierSettings() {
181  if (message_center_delegate_) {
182    message_center_delegate_->SetSettingsVisible(true);
183    return true;
184  }
185  message_center_delegate_ =
186      new MessageCenterWidgetDelegate(this,
187                                      message_center_tray_.get(),
188                                      true,  // settings initally visible
189                                      GetPositionInfo());
190
191  return true;
192}
193
194bool WebNotificationTray::IsContextMenuEnabled() const {
195  // It can always return true because the notifications are invisible if
196  // the context menu shouldn't be enabled, such as in the lock screen.
197  return true;
198}
199
200void WebNotificationTray::OnMessageCenterTrayChanged() {
201  if (status_icon_) {
202    bool quiet_mode_state = message_center()->IsQuietMode();
203    if (last_quiet_mode_state_ != quiet_mode_state) {
204      last_quiet_mode_state_ = quiet_mode_state;
205
206      // Quiet mode has changed, update the quiet mode menu.
207      status_icon_menu_->SetCommandIdChecked(kToggleQuietMode,
208                                             quiet_mode_state);
209    }
210  }
211
212  // See the comments in ash/system/web_notification/web_notification_tray.cc
213  // for why PostTask.
214  should_update_tray_content_ = true;
215  base::MessageLoop::current()->PostTask(
216      FROM_HERE,
217      base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr()));
218}
219
220void WebNotificationTray::OnStatusIconClicked() {
221  // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura.
222  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
223  mouse_click_point_ = screen->GetCursorScreenPoint();
224  message_center_tray_->ToggleMessageCenterBubble();
225}
226
227void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) {
228  if (command_id == kToggleQuietMode) {
229    bool in_quiet_mode = message_center()->IsQuietMode();
230    message_center()->SetQuietMode(!in_quiet_mode);
231    return;
232  }
233  base::TimeDelta expires_in = command_id == kEnableQuietModeDay
234                                   ? base::TimeDelta::FromDays(1)
235                                   : base::TimeDelta::FromHours(1);
236  message_center()->EnterQuietModeWithExpire(expires_in);
237}
238
239void WebNotificationTray::UpdateStatusIcon() {
240  if (!should_update_tray_content_)
241    return;
242  should_update_tray_content_ = false;
243
244  int unread_notifications = message_center()->UnreadNotificationCount();
245
246  base::string16 tool_tip;
247  if (unread_notifications > 0) {
248    base::string16 str_unread_count = base::FormatNumber(unread_notifications);
249    tool_tip = l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP_UNREAD,
250                                          str_unread_count);
251  } else {
252    tool_tip = l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_TOOLTIP);
253  }
254
255  gfx::ImageSkia* icon_image = GetIcon(
256      unread_notifications,
257      message_center()->IsQuietMode());
258
259  if (status_icon_) {
260    status_icon_->SetImage(*icon_image);
261    status_icon_->SetToolTip(tool_tip);
262    return;
263  }
264
265  CreateStatusIcon(*icon_image, tool_tip);
266}
267
268void WebNotificationTray::SendHideMessageCenter() {
269  message_center_tray_->HideMessageCenterBubble();
270}
271
272void WebNotificationTray::MarkMessageCenterHidden() {
273  if (message_center_delegate_) {
274    message_center_tray_->MarkMessageCenterHidden();
275    message_center_delegate_ = NULL;
276  }
277}
278
279PositionInfo WebNotificationTray::GetPositionInfo() {
280  PositionInfo pos_info;
281
282  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
283  gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
284  work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
285
286  gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_);
287
288  pos_info.taskbar_alignment = internal::GetTaskbarAlignment();
289
290  // We assume the taskbar is either at the top or at the bottom if we are not
291  // able to find it.
292  if (pos_info.taskbar_alignment == ALIGNMENT_NONE) {
293    if (mouse_click_point_.y() > corner.y())
294      pos_info.taskbar_alignment = ALIGNMENT_TOP;
295    else
296      pos_info.taskbar_alignment = ALIGNMENT_BOTTOM;
297  }
298
299  pos_info.message_center_alignment =
300      internal::GetAnchorAlignment(work_area, corner);
301
302  pos_info.inital_anchor_point = corner;
303  pos_info.max_height = work_area.height();
304
305  if (work_area.Contains(mouse_click_point_)) {
306    // Message center is in the work area. So position it few pixels above the
307    // mouse click point if alignemnt is towards bottom and few pixels below if
308    // alignment is towards top.
309    pos_info.inital_anchor_point.set_y(
310        mouse_click_point_.y() +
311        (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -kMouseOffset
312                                                              : kMouseOffset));
313
314    // Subtract the distance between mouse click point and the closest
315    // (insetted) edge from the max height to show the message center within the
316    // (insetted) work area bounds. Also subtract the offset from the mouse
317    // click point we added earlier.
318    pos_info.max_height -=
319        std::abs(mouse_click_point_.y() - corner.y()) + kMouseOffset;
320  }
321  return pos_info;
322}
323
324MessageCenterTray* WebNotificationTray::GetMessageCenterTray() {
325  return message_center_tray_.get();
326}
327
328void WebNotificationTray::CreateStatusIcon(const gfx::ImageSkia& image,
329                                           const base::string16& tool_tip) {
330  if (status_icon_)
331    return;
332
333  StatusTray* status_tray = g_browser_process->status_tray();
334  if (!status_tray)
335    return;
336
337  status_icon_ = status_tray->CreateStatusIcon(
338      StatusTray::NOTIFICATION_TRAY_ICON, image, tool_tip);
339  if (!status_icon_)
340    return;
341
342  status_icon_->AddObserver(this);
343  AddQuietModeMenu(status_icon_);
344}
345
346void WebNotificationTray::DestroyStatusIcon() {
347  if (!status_icon_)
348    return;
349
350  status_icon_->RemoveObserver(this);
351  StatusTray* status_tray = g_browser_process->status_tray();
352  if (status_tray)
353    status_tray->RemoveStatusIcon(status_icon_);
354  status_icon_menu_ = NULL;
355  status_icon_ = NULL;
356}
357
358void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) {
359  DCHECK(status_icon);
360
361  scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
362  menu->AddCheckItem(kToggleQuietMode,
363                     l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE));
364  menu->SetCommandIdChecked(kToggleQuietMode, message_center()->IsQuietMode());
365  menu->AddItem(kEnableQuietModeHour,
366                l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1HOUR));
367  menu->AddItem(kEnableQuietModeDay,
368                l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1DAY));
369
370  status_icon_menu_ = menu.get();
371  status_icon->SetContextMenu(menu.Pass());
372}
373
374MessageCenterWidgetDelegate*
375WebNotificationTray::GetMessageCenterWidgetDelegateForTest() {
376  return message_center_delegate_;
377}
378
379}  // namespace message_center
380