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.
5 #include "chrome/browser/ui/views/message_center/web_notification_tray.h"
7 #include "base/i18n/number_formatting.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/strings/string16.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/browser_process.h"
12 #include "chrome/browser/status_icons/status_icon.h"
13 #include "chrome/browser/status_icons/status_icon_menu_model.h"
14 #include "chrome/browser/status_icons/status_tray.h"
15 #include "chrome/common/pref_names.h"
16 #include "chrome/grit/chromium_strings.h"
17 #include "chrome/grit/generated_resources.h"
18 #include "content/public/browser/notification_service.h"
19 #include "grit/theme_resources.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/geometry/rect.h"
24 #include "ui/gfx/geometry/size.h"
25 #include "ui/gfx/image/image_skia_operations.h"
26 #include "ui/gfx/screen.h"
27 #include "ui/message_center/message_center_tray.h"
28 #include "ui/message_center/message_center_tray_delegate.h"
29 #include "ui/message_center/views/desktop_popup_alignment_delegate.h"
30 #include "ui/message_center/views/message_popup_collection.h"
31 #include "ui/strings/grit/ui_strings.h"
32 #include "ui/views/widget/widget.h"
35 #include "base/environment.h"
36 #include "base/nix/xdg_util.h"
42 const int kScreenEdgePadding
= 2;
44 // Number of pixels the message center is offset from the mouse.
45 const int kMouseOffset
= 5;
48 const int kToggleQuietMode
= 0;
49 const int kEnableQuietModeHour
= 1;
50 const int kEnableQuietModeDay
= 2;
52 gfx::ImageSkia
* GetIcon(int unread_count
, bool is_quiet_mode
) {
53 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
54 int resource_id
= IDR_NOTIFICATION_TRAY_EMPTY
;
58 resource_id
= IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_ATTENTION
;
60 resource_id
= IDR_NOTIFICATION_TRAY_ATTENTION
;
61 } else if (is_quiet_mode
) {
62 resource_id
= IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_EMPTY
;
65 return rb
.GetImageSkiaNamed(resource_id
);
68 bool CanDestroyStatusIcon() {
70 // Avoid creating multiple system tray icons on KDE4 and newer versions of KDE
71 // because the OS does not support removing system tray icons.
72 // TODO(pkotwicz): This is a hack for the sake of M40. Fix this properly.
73 scoped_ptr
<base::Environment
> env(base::Environment::Create());
74 base::nix::DesktopEnvironment desktop_environment
=
75 base::nix::GetDesktopEnvironment(env
.get());
76 return desktop_environment
!= base::nix::DESKTOP_ENVIRONMENT_KDE4
;
84 namespace message_center
{
88 // Gets the position of the taskbar from the work area bounds. Returns
89 // ALIGNMENT_NONE if position cannot be found.
90 Alignment
GetTaskbarAlignment() {
91 gfx::Screen
* screen
= gfx::Screen::GetNativeScreen();
92 // TODO(dewittj): It's possible GetPrimaryDisplay is wrong.
93 gfx::Rect screen_bounds
= screen
->GetPrimaryDisplay().bounds();
94 gfx::Rect work_area
= screen
->GetPrimaryDisplay().work_area();
95 work_area
.Inset(kScreenEdgePadding
, kScreenEdgePadding
);
97 // Comparing the work area to the screen bounds gives us the location of the
98 // taskbar. If the work area is exactly the same as the screen bounds,
99 // we are unable to locate the taskbar so we say we don't know it's alignment.
100 if (work_area
.height() < screen_bounds
.height()) {
101 if (work_area
.y() > screen_bounds
.y())
102 return ALIGNMENT_TOP
;
103 return ALIGNMENT_BOTTOM
;
105 if (work_area
.width() < screen_bounds
.width()) {
106 if (work_area
.x() > screen_bounds
.x())
107 return ALIGNMENT_LEFT
;
108 return ALIGNMENT_RIGHT
;
111 return ALIGNMENT_NONE
;
114 gfx::Point
GetClosestCorner(const gfx::Rect
& rect
, const gfx::Point
& query
) {
115 gfx::Point center_point
= rect
.CenterPoint();
118 if (query
.x() > center_point
.x())
119 rv
.set_x(rect
.right());
123 if (query
.y() > center_point
.y())
124 rv
.set_y(rect
.bottom());
131 // Gets the corner of the screen where the message center should pop up.
132 Alignment
GetAnchorAlignment(const gfx::Rect
& work_area
, gfx::Point corner
) {
133 gfx::Point center
= work_area
.CenterPoint();
135 Alignment anchor_alignment
=
136 center
.y() > corner
.y() ? ALIGNMENT_TOP
: ALIGNMENT_BOTTOM
;
138 (Alignment
)(anchor_alignment
|
139 (center
.x() > corner
.x() ? ALIGNMENT_LEFT
: ALIGNMENT_RIGHT
));
141 return anchor_alignment
;
144 } // namespace internal
146 MessageCenterTrayDelegate
* CreateMessageCenterTray() {
147 return new WebNotificationTray(g_browser_process
->local_state());
150 WebNotificationTray::WebNotificationTray(PrefService
* local_state
)
151 : message_center_delegate_(NULL
),
153 status_icon_menu_(NULL
),
154 should_update_tray_content_(true) {
155 message_center_tray_
.reset(
156 new MessageCenterTray(this, g_browser_process
->message_center()));
157 last_quiet_mode_state_
= message_center()->IsQuietMode();
158 alignment_delegate_
.reset(new message_center::DesktopPopupAlignmentDelegate
);
159 popup_collection_
.reset(new message_center::MessagePopupCollection(
160 NULL
, message_center(), message_center_tray_
.get(),
161 alignment_delegate_
.get()));
164 // |local_state| can be NULL in tests.
166 did_force_tray_visible_
.reset(new BooleanPrefMember());
167 did_force_tray_visible_
->Init(prefs::kMessageCenterForcedOnTaskbar
,
171 title_
= l10n_util::GetStringFUTF16(
172 IDS_MESSAGE_CENTER_FOOTER_WITH_PRODUCT_TITLE
,
173 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME
));
176 WebNotificationTray::~WebNotificationTray() {
177 // Reset this early so that delegated events during destruction don't cause
179 popup_collection_
.reset();
180 message_center_tray_
.reset();
184 message_center::MessageCenter
* WebNotificationTray::message_center() {
185 return message_center_tray_
->message_center();
188 bool WebNotificationTray::ShowPopups() {
189 alignment_delegate_
->StartObserving(gfx::Screen::GetNativeScreen());
190 popup_collection_
->DoUpdateIfPossible();
194 void WebNotificationTray::HidePopups() {
195 DCHECK(popup_collection_
.get());
196 popup_collection_
->MarkAllPopupsShown();
199 bool WebNotificationTray::ShowMessageCenter() {
200 message_center_delegate_
=
201 new MessageCenterWidgetDelegate(this,
202 message_center_tray_
.get(),
203 false, // settings initally invisible
210 void WebNotificationTray::HideMessageCenter() {
211 if (message_center_delegate_
) {
212 views::Widget
* widget
= message_center_delegate_
->GetWidget();
218 bool WebNotificationTray::ShowNotifierSettings() {
219 if (message_center_delegate_
) {
220 message_center_delegate_
->SetSettingsVisible(true);
223 message_center_delegate_
=
224 new MessageCenterWidgetDelegate(this,
225 message_center_tray_
.get(),
226 true, // settings initally visible
233 bool WebNotificationTray::IsContextMenuEnabled() const {
234 // It can always return true because the notifications are invisible if
235 // the context menu shouldn't be enabled, such as in the lock screen.
239 void WebNotificationTray::OnMessageCenterTrayChanged() {
241 bool quiet_mode_state
= message_center()->IsQuietMode();
242 if (last_quiet_mode_state_
!= quiet_mode_state
) {
243 last_quiet_mode_state_
= quiet_mode_state
;
245 // Quiet mode has changed, update the quiet mode menu.
246 status_icon_menu_
->SetCommandIdChecked(kToggleQuietMode
,
249 } else if (message_center()->NotificationCount() == 0) {
250 // If there's no existing status icon and we still don't have any
251 // notifications to display, nothing needs to be done.
255 // See the comments in ash/system/web_notification/web_notification_tray.cc
257 should_update_tray_content_
= true;
258 base::MessageLoop::current()->PostTask(
260 base::Bind(&WebNotificationTray::UpdateStatusIcon
, AsWeakPtr()));
263 void WebNotificationTray::OnStatusIconClicked() {
264 // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura.
265 gfx::Screen
* screen
= gfx::Screen::GetNativeScreen();
266 mouse_click_point_
= screen
->GetCursorScreenPoint();
267 message_center_tray_
->ToggleMessageCenterBubble();
270 void WebNotificationTray::ExecuteCommand(int command_id
, int event_flags
) {
271 if (command_id
== kToggleQuietMode
) {
272 bool in_quiet_mode
= message_center()->IsQuietMode();
273 message_center()->SetQuietMode(!in_quiet_mode
);
276 base::TimeDelta expires_in
= command_id
== kEnableQuietModeDay
277 ? base::TimeDelta::FromDays(1)
278 : base::TimeDelta::FromHours(1);
279 message_center()->EnterQuietModeWithExpire(expires_in
);
282 void WebNotificationTray::UpdateStatusIcon() {
283 if (!should_update_tray_content_
)
285 should_update_tray_content_
= false;
287 int unread_notifications
= message_center()->UnreadNotificationCount();
289 base::string16 tool_tip
;
290 if (unread_notifications
> 0) {
291 base::string16 str_unread_count
= base::FormatNumber(unread_notifications
);
292 tool_tip
= l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP_UNREAD
,
295 tool_tip
= l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_TOOLTIP
);
298 if (message_center()->GetVisibleNotifications().empty() &&
299 CanDestroyStatusIcon()) {
304 gfx::ImageSkia
* icon_image
= GetIcon(
305 unread_notifications
,
306 message_center()->IsQuietMode());
309 status_icon_
->SetImage(*icon_image
);
310 status_icon_
->SetToolTip(tool_tip
);
314 CreateStatusIcon(*icon_image
, tool_tip
);
317 void WebNotificationTray::SendHideMessageCenter() {
318 message_center_tray_
->HideMessageCenterBubble();
321 void WebNotificationTray::MarkMessageCenterHidden() {
322 if (message_center_delegate_
) {
323 message_center_tray_
->MarkMessageCenterHidden();
324 message_center_delegate_
= NULL
;
328 PositionInfo
WebNotificationTray::GetPositionInfo() {
329 PositionInfo pos_info
;
331 gfx::Screen
* screen
= gfx::Screen::GetNativeScreen();
332 gfx::Rect work_area
= screen
->GetPrimaryDisplay().work_area();
333 work_area
.Inset(kScreenEdgePadding
, kScreenEdgePadding
);
335 gfx::Point corner
= internal::GetClosestCorner(work_area
, mouse_click_point_
);
337 pos_info
.taskbar_alignment
= internal::GetTaskbarAlignment();
339 // We assume the taskbar is either at the top or at the bottom if we are not
341 if (pos_info
.taskbar_alignment
== ALIGNMENT_NONE
) {
342 if (mouse_click_point_
.y() > corner
.y())
343 pos_info
.taskbar_alignment
= ALIGNMENT_TOP
;
345 pos_info
.taskbar_alignment
= ALIGNMENT_BOTTOM
;
348 pos_info
.message_center_alignment
=
349 internal::GetAnchorAlignment(work_area
, corner
);
351 pos_info
.inital_anchor_point
= corner
;
352 pos_info
.max_height
= work_area
.height();
354 if (work_area
.Contains(mouse_click_point_
)) {
355 // Message center is in the work area. So position it few pixels above the
356 // mouse click point if alignemnt is towards bottom and few pixels below if
357 // alignment is towards top.
358 pos_info
.inital_anchor_point
.set_y(
359 mouse_click_point_
.y() +
360 (pos_info
.message_center_alignment
& ALIGNMENT_BOTTOM
? -kMouseOffset
363 // Subtract the distance between mouse click point and the closest
364 // (insetted) edge from the max height to show the message center within the
365 // (insetted) work area bounds. Also subtract the offset from the mouse
366 // click point we added earlier.
367 pos_info
.max_height
-=
368 std::abs(mouse_click_point_
.y() - corner
.y()) + kMouseOffset
;
373 MessageCenterTray
* WebNotificationTray::GetMessageCenterTray() {
374 return message_center_tray_
.get();
377 void WebNotificationTray::CreateStatusIcon(const gfx::ImageSkia
& image
,
378 const base::string16
& tool_tip
) {
382 StatusTray
* status_tray
= g_browser_process
->status_tray();
386 status_icon_
= status_tray
->CreateStatusIcon(
387 StatusTray::NOTIFICATION_TRAY_ICON
, image
, tool_tip
);
391 status_icon_
->AddObserver(this);
392 AddQuietModeMenu(status_icon_
);
395 void WebNotificationTray::DestroyStatusIcon() {
399 status_icon_
->RemoveObserver(this);
400 StatusTray
* status_tray
= g_browser_process
->status_tray();
402 status_tray
->RemoveStatusIcon(status_icon_
);
403 status_icon_menu_
= NULL
;
407 void WebNotificationTray::AddQuietModeMenu(StatusIcon
* status_icon
) {
410 scoped_ptr
<StatusIconMenuModel
> menu(new StatusIconMenuModel(this));
411 menu
->AddCheckItem(kToggleQuietMode
,
412 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE
));
413 menu
->SetCommandIdChecked(kToggleQuietMode
, message_center()->IsQuietMode());
414 menu
->AddItem(kEnableQuietModeHour
,
415 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1HOUR
));
416 menu
->AddItem(kEnableQuietModeDay
,
417 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1DAY
));
419 status_icon_menu_
= menu
.get();
420 status_icon
->SetContextMenu(menu
.Pass());
423 MessageCenterWidgetDelegate
*
424 WebNotificationTray::GetMessageCenterWidgetDelegateForTest() {
425 return message_center_delegate_
;
428 } // namespace message_center