web_notification_tray_win.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 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_win.h"
6
7#include "base/i18n/number_formatting.h"
8#include "base/string16.h"
9#include "base/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 "chrome/browser/ui/views/message_center/notification_bubble_wrapper_win.h"
14#include "chrome/browser/ui/views/status_icons/status_icon_win.h"
15#include "content/public/browser/user_metrics.h"
16#include "grit/chromium_strings.h"
17#include "grit/theme_resources.h"
18#include "grit/ui_strings.h"
19#include "ui/base/l10n/l10n_util.h"
20#include "ui/base/models/simple_menu_model.h"
21#include "ui/base/resource/resource_bundle.h"
22#include "ui/base/win/hwnd_util.h"
23#include "ui/gfx/canvas.h"
24#include "ui/gfx/image/image_skia_operations.h"
25#include "ui/gfx/rect.h"
26#include "ui/gfx/screen.h"
27#include "ui/gfx/size.h"
28#include "ui/message_center/message_center_tray.h"
29#include "ui/message_center/message_center_tray_delegate.h"
30#include "ui/message_center/views/message_bubble_base.h"
31#include "ui/message_center/views/message_center_bubble.h"
32#include "ui/message_center/views/message_popup_collection.h"
33#include "ui/views/widget/widget.h"
34
35namespace {
36
37// Tray constants
38const int kScreenEdgePadding = 2;
39
40const int kSystemTrayWidth = 16;
41const int kSystemTrayHeight = 16;
42const int kNumberOfSystemTraySprites = 10;
43
44gfx::Rect GetCornerAnchorRect() {
45  // TODO(dewittj): Use the preference to determine which corner to anchor from.
46  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
47  gfx::Rect rect = screen->GetPrimaryDisplay().work_area();
48  rect.Inset(kScreenEdgePadding, kScreenEdgePadding);
49  return gfx::Rect(rect.bottom_right(), gfx::Size());
50}
51
52gfx::Point GetClosestCorner(gfx::Rect rect, gfx::Point query) {
53  gfx::Point center_point = rect.CenterPoint();
54  gfx::Point rv;
55
56  if (query.x() > center_point.x())
57    rv.set_x(rect.right());
58  else
59    rv.set_x(rect.x());
60
61  if (query.y() > center_point.y())
62    rv.set_y(rect.bottom());
63  else
64    rv.set_y(rect.y());
65
66  return rv;
67}
68
69// GetMouseAnchorRect returns a rectangle that has one corner where the mouse
70// clicked, and the opposite corner at the closest corner of the work area
71// (inset by an appropriate margin.)
72gfx::Rect GetMouseAnchorRect(gfx::Point cursor) {
73  // TODO(dewittj): GetNativeScreen could be wrong for Aura.
74  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
75  gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
76  work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
77  gfx::Point corner = GetClosestCorner(work_area, cursor);
78
79  gfx::Rect mouse_anchor_rect(gfx::BoundingRect(cursor, corner));
80  return mouse_anchor_rect;
81}
82
83gfx::ImageSkia GetIcon(int unread_count) {
84  bool has_unread = unread_count > 0;
85  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
86  if (!has_unread)
87    return *rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_EMPTY);
88
89  // TODO(dewittj): Use scale factors other than 100P.
90  scoped_ptr<gfx::Canvas> canvas(new gfx::Canvas(
91      gfx::Size(kSystemTrayWidth, kSystemTrayHeight),
92      ui::SCALE_FACTOR_100P,
93      false));
94
95  // Draw the attention-grabbing background image.
96  canvas->DrawImageInt(
97      *rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_ATTENTION), 0, 0);
98
99  // |numbers| is a sprite map with the image of a number from 1-9 and 9+. They
100  // are arranged horizontally, and have a transparent background.
101  gfx::ImageSkia* numbers = rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_NUMBERS);
102
103  // Assume that the last sprite is the catch-all for higher numbers of
104  // notifications.
105  int effective_unread = std::min(unread_count, kNumberOfSystemTraySprites);
106  int x_offset = (effective_unread - 1) * kSystemTrayWidth;
107
108  canvas->DrawImageInt(*numbers,
109                       x_offset, 0, kSystemTrayWidth, kSystemTrayHeight,
110                       0, 0, kSystemTrayWidth, kSystemTrayHeight,
111                       false);
112
113  return gfx::ImageSkia(canvas->ExtractImageRep());
114}
115
116}  // namespace
117
118using content::UserMetricsAction;
119
120namespace message_center {
121
122MessageCenterTrayDelegate* CreateMessageCenterTray() {
123  return new WebNotificationTrayWin();
124}
125
126WebNotificationTrayWin::WebNotificationTrayWin()
127    : status_icon_(NULL),
128      message_center_visible_(false),
129      should_update_tray_content_(true) {
130  message_center_tray_.reset(new MessageCenterTray(
131      this, g_browser_process->message_center()));
132  UpdateStatusIcon();
133}
134
135WebNotificationTrayWin::~WebNotificationTrayWin() {
136  // Reset this early so that delegated events during destruction don't cause
137  // problems.
138  message_center_tray_.reset();
139  DestroyStatusIcon();
140}
141
142message_center::MessageCenter* WebNotificationTrayWin::message_center() {
143  return message_center_tray_->message_center();
144}
145
146bool WebNotificationTrayWin::ShowPopups() {
147  popup_collection_.reset(
148      new message_center::MessagePopupCollection(NULL, message_center()));
149  return true;
150}
151
152void WebNotificationTrayWin::HidePopups() {
153  popup_collection_.reset();
154}
155
156bool WebNotificationTrayWin::ShowMessageCenter() {
157  content::RecordAction(UserMetricsAction("Notifications.ShowMessageCenter"));
158
159  scoped_ptr<message_center::MessageCenterBubble> bubble(
160      new message_center::MessageCenterBubble(message_center()));
161  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
162  gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
163  views::TrayBubbleView::AnchorAlignment alignment = GetAnchorAlignment();
164
165  int max_height = work_area.height();
166
167  // If the alignment is left- or right-oriented, the bubble can fill up the
168  // entire vertical height of the screen since the bubble is rendered to the
169  // side of the clicked icon.  Otherwise we have to adjust for the arrow's
170  // height.
171  if (alignment == views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM ||
172      alignment == views::TrayBubbleView::ANCHOR_ALIGNMENT_TOP) {
173    max_height -= 2*kScreenEdgePadding;
174
175    // If the work area contains the click point, then we know that the icon is
176    // not in the taskbar.  Then we need to subtract the distance of the click
177    // point from the edge of the work area so we can see the whole bubble.
178    if (work_area.Contains(mouse_click_point_)) {
179      max_height -= std::min(mouse_click_point_.y() - work_area.y(),
180                             work_area.bottom() - mouse_click_point_.y());
181    }
182  }
183  bubble->SetMaxHeight(max_height);
184
185  message_center_bubble_.reset(new internal::NotificationBubbleWrapperWin(
186      this,
187      bubble.Pass(),
188      internal::NotificationBubbleWrapperWin::BUBBLE_TYPE_MESSAGE_CENTER));
189  return true;
190}
191
192void WebNotificationTrayWin::HideMessageCenter() {
193  message_center_bubble_.reset();
194}
195
196void WebNotificationTrayWin::UpdatePopups() {
197  // |popup_collection_| receives notification add/remove events and updates
198  // itself, so this method doesn't need to do anything.
199  // TODO(mukai): remove this method (currently this is used by
200  // non-rich-notifications in ChromeOS).
201};
202
203void WebNotificationTrayWin::OnMessageCenterTrayChanged() {
204  // See the comments in ash/system/web_notification/web_notification_tray.cc
205  // for why PostTask.
206  should_update_tray_content_ = true;
207  MessageLoop::current()->PostTask(
208      FROM_HERE,
209      base::Bind(&WebNotificationTrayWin::UpdateStatusIcon, AsWeakPtr()));
210}
211
212gfx::Rect WebNotificationTrayWin::GetMessageCenterAnchor() {
213  return GetMouseAnchorRect(mouse_click_point_);
214}
215
216gfx::Rect WebNotificationTrayWin::GetPopupAnchor() {
217  return GetCornerAnchorRect();
218}
219
220views::TrayBubbleView::AnchorAlignment
221WebNotificationTrayWin::GetAnchorAlignment() {
222  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
223  // TODO(dewittj): It's possible GetPrimaryDisplay is wrong.
224  gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds();
225  gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
226
227  // Comparing the work area to the screen bounds gives us the location of the
228  // taskbar.  If the work area is less tall than the screen, assume the taskbar
229  // is on the bottom, and cause the arrow to be displayed on the bottom of the
230  // bubble.  Otherwise, cause the arrow to be displayed on the side of the
231  // bubble that the taskbar is on.
232  if (work_area.width() < screen_bounds.width()) {
233    if (work_area.x() > screen_bounds.x())
234      return views::TrayBubbleView::ANCHOR_ALIGNMENT_LEFT;
235    return views::TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT;
236  }
237  return views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM;
238}
239
240gfx::NativeView WebNotificationTrayWin::GetBubbleWindowContainer() {
241  return NULL;
242}
243
244void WebNotificationTrayWin::UpdateStatusIcon() {
245  if (!should_update_tray_content_)
246    return;
247  should_update_tray_content_ = false;
248
249  int total_notifications = message_center()->NotificationCount();
250  if (total_notifications == 0) {
251    DestroyStatusIcon();
252    return;
253  }
254
255  int unread_notifications = message_center()->UnreadNotificationCount();
256  StatusIcon* status_icon = GetStatusIcon();
257  if (!status_icon)
258    return;
259
260  status_icon->SetImage(GetIcon(unread_notifications));
261
262  string16 product_name(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
263  if (unread_notifications > 0) {
264    string16 str_unread_count = base::FormatNumber(unread_notifications);
265    status_icon->SetToolTip(l10n_util::GetStringFUTF16(
266        IDS_MESSAGE_CENTER_TOOLTIP_UNREAD, product_name, str_unread_count));
267  } else {
268    status_icon->SetToolTip(l10n_util::GetStringFUTF16(
269        IDS_MESSAGE_CENTER_TOOLTIP, product_name));
270  }
271}
272
273void WebNotificationTrayWin::OnStatusIconClicked() {
274  // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura.
275  gfx::Screen* screen = gfx::Screen::GetNativeScreen();
276  mouse_click_point_ = screen->GetCursorScreenPoint();
277  message_center_tray_->ToggleMessageCenterBubble();
278}
279
280void WebNotificationTrayWin::HideBubbleWithView(
281    const views::TrayBubbleView* bubble_view) {
282  if (message_center_bubble_.get() &&
283      bubble_view == message_center_bubble_->bubble_view()) {
284    message_center_tray_->HideMessageCenterBubble();
285  }
286}
287
288StatusIcon* WebNotificationTrayWin::GetStatusIcon() {
289  if (status_icon_)
290    return status_icon_;
291
292  StatusTray* status_tray = g_browser_process->status_tray();
293  if (!status_tray)
294    return NULL;
295
296  StatusIcon* status_icon = status_tray->CreateStatusIcon();
297  if (!status_icon)
298    return NULL;
299
300  status_icon_ = status_icon;
301  status_icon_->AddObserver(this);
302  AddQuietModeMenu(status_icon_);
303
304  return status_icon_;
305}
306
307void WebNotificationTrayWin::DestroyStatusIcon() {
308  if (!status_icon_)
309    return;
310
311  status_icon_->RemoveObserver(this);
312  StatusTray* status_tray = g_browser_process->status_tray();
313  if (status_tray)
314    status_tray->RemoveStatusIcon(status_icon_);
315  status_icon_ = NULL;
316}
317
318void WebNotificationTrayWin::AddQuietModeMenu(StatusIcon* status_icon) {
319  DCHECK(status_icon);
320  status_icon->SetContextMenu(message_center_tray_->CreateQuietModeMenu());
321}
322
323message_center::MessageCenterBubble*
324WebNotificationTrayWin::GetMessageCenterBubbleForTest() {
325  if (!message_center_bubble_.get())
326    return NULL;
327  return static_cast<message_center::MessageCenterBubble*>(
328      message_center_bubble_->bubble());
329}
330
331}  // namespace message_center
332