Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / message_center / web_notification_tray.cc
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/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/image/image_skia_operations.h"
24 #include "ui/gfx/rect.h"
25 #include "ui/gfx/screen.h"
26 #include "ui/gfx/size.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"
33
34 namespace {
35
36 // Tray constants
37 const int kScreenEdgePadding = 2;
38
39 // Number of pixels the message center is offset from the mouse.
40 const int kMouseOffset = 5;
41
42 // Menu commands
43 const int kToggleQuietMode = 0;
44 const int kEnableQuietModeHour = 1;
45 const int kEnableQuietModeDay = 2;
46
47 gfx::ImageSkia* GetIcon(int unread_count, bool is_quiet_mode) {
48   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
49   int resource_id = IDR_NOTIFICATION_TRAY_EMPTY;
50
51   if (unread_count) {
52     if (is_quiet_mode)
53       resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_ATTENTION;
54     else
55       resource_id = IDR_NOTIFICATION_TRAY_ATTENTION;
56   } else if (is_quiet_mode) {
57     resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_EMPTY;
58   }
59
60   return rb.GetImageSkiaNamed(resource_id);
61 }
62
63 }  // namespace
64
65 namespace message_center {
66
67 namespace internal {
68
69 // Gets the position of the taskbar from the work area bounds. Returns
70 // ALIGNMENT_NONE if position cannot be found.
71 Alignment GetTaskbarAlignment() {
72   gfx::Screen* screen = gfx::Screen::GetNativeScreen();
73   // TODO(dewittj): It's possible GetPrimaryDisplay is wrong.
74   gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds();
75   gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
76   work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
77
78   // Comparing the work area to the screen bounds gives us the location of the
79   // taskbar.  If the work area is exactly the same as the screen bounds,
80   // we are unable to locate the taskbar so we say we don't know it's alignment.
81   if (work_area.height() < screen_bounds.height()) {
82     if (work_area.y() > screen_bounds.y())
83       return ALIGNMENT_TOP;
84     return ALIGNMENT_BOTTOM;
85   }
86   if (work_area.width() < screen_bounds.width()) {
87     if (work_area.x() > screen_bounds.x())
88       return ALIGNMENT_LEFT;
89     return ALIGNMENT_RIGHT;
90   }
91
92   return ALIGNMENT_NONE;
93 }
94
95 gfx::Point GetClosestCorner(const gfx::Rect& rect, const gfx::Point& query) {
96   gfx::Point center_point = rect.CenterPoint();
97   gfx::Point rv;
98
99   if (query.x() > center_point.x())
100     rv.set_x(rect.right());
101   else
102     rv.set_x(rect.x());
103
104   if (query.y() > center_point.y())
105     rv.set_y(rect.bottom());
106   else
107     rv.set_y(rect.y());
108
109   return rv;
110 }
111
112 // Gets the corner of the screen where the message center should pop up.
113 Alignment GetAnchorAlignment(const gfx::Rect& work_area, gfx::Point corner) {
114   gfx::Point center = work_area.CenterPoint();
115
116   Alignment anchor_alignment =
117       center.y() > corner.y() ? ALIGNMENT_TOP : ALIGNMENT_BOTTOM;
118   anchor_alignment =
119       (Alignment)(anchor_alignment |
120                   (center.x() > corner.x() ? ALIGNMENT_LEFT : ALIGNMENT_RIGHT));
121
122   return anchor_alignment;
123 }
124
125 }  // namespace internal
126
127 MessageCenterTrayDelegate* CreateMessageCenterTray() {
128   return new WebNotificationTray(g_browser_process->local_state());
129 }
130
131 WebNotificationTray::WebNotificationTray(PrefService* local_state)
132     : message_center_delegate_(NULL),
133       status_icon_(NULL),
134       status_icon_menu_(NULL),
135       should_update_tray_content_(true) {
136   message_center_tray_.reset(
137       new MessageCenterTray(this, g_browser_process->message_center()));
138   last_quiet_mode_state_ = message_center()->IsQuietMode();
139   alignment_delegate_.reset(new message_center::DesktopPopupAlignmentDelegate);
140   popup_collection_.reset(new message_center::MessagePopupCollection(
141       NULL, message_center(), message_center_tray_.get(),
142       alignment_delegate_.get()));
143
144 #if defined(OS_WIN)
145   // |local_state| can be NULL in tests.
146   if (local_state) {
147     did_force_tray_visible_.reset(new BooleanPrefMember());
148     did_force_tray_visible_->Init(prefs::kMessageCenterForcedOnTaskbar,
149                                   local_state);
150   }
151 #endif
152   title_ = l10n_util::GetStringFUTF16(
153       IDS_MESSAGE_CENTER_FOOTER_WITH_PRODUCT_TITLE,
154       l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
155 }
156
157 WebNotificationTray::~WebNotificationTray() {
158   // Reset this early so that delegated events during destruction don't cause
159   // problems.
160   popup_collection_.reset();
161   message_center_tray_.reset();
162   DestroyStatusIcon();
163 }
164
165 message_center::MessageCenter* WebNotificationTray::message_center() {
166   return message_center_tray_->message_center();
167 }
168
169 bool WebNotificationTray::ShowPopups() {
170   alignment_delegate_->StartObserving(gfx::Screen::GetNativeScreen());
171   popup_collection_->DoUpdateIfPossible();
172   return true;
173 }
174
175 void WebNotificationTray::HidePopups() {
176   DCHECK(popup_collection_.get());
177   popup_collection_->MarkAllPopupsShown();
178 }
179
180 bool WebNotificationTray::ShowMessageCenter() {
181   message_center_delegate_ =
182       new MessageCenterWidgetDelegate(this,
183                                       message_center_tray_.get(),
184                                       false,  // settings initally invisible
185                                       GetPositionInfo(),
186                                       title_);
187
188   return true;
189 }
190
191 void WebNotificationTray::HideMessageCenter() {
192   if (message_center_delegate_) {
193     views::Widget* widget = message_center_delegate_->GetWidget();
194     if (widget)
195       widget->Close();
196   }
197 }
198
199 bool WebNotificationTray::ShowNotifierSettings() {
200   if (message_center_delegate_) {
201     message_center_delegate_->SetSettingsVisible(true);
202     return true;
203   }
204   message_center_delegate_ =
205       new MessageCenterWidgetDelegate(this,
206                                       message_center_tray_.get(),
207                                       true,  // settings initally visible
208                                       GetPositionInfo(),
209                                       title_);
210
211   return true;
212 }
213
214 bool WebNotificationTray::IsContextMenuEnabled() const {
215   // It can always return true because the notifications are invisible if
216   // the context menu shouldn't be enabled, such as in the lock screen.
217   return true;
218 }
219
220 void WebNotificationTray::OnMessageCenterTrayChanged() {
221   if (status_icon_) {
222     bool quiet_mode_state = message_center()->IsQuietMode();
223     if (last_quiet_mode_state_ != quiet_mode_state) {
224       last_quiet_mode_state_ = quiet_mode_state;
225
226       // Quiet mode has changed, update the quiet mode menu.
227       status_icon_menu_->SetCommandIdChecked(kToggleQuietMode,
228                                              quiet_mode_state);
229     }
230   } else if (message_center()->NotificationCount() == 0) {
231     // If there's no existing status icon and we still don't have any
232     // notifications to display, nothing needs to be done.
233     return;
234   }
235
236   // See the comments in ash/system/web_notification/web_notification_tray.cc
237   // for why PostTask.
238   should_update_tray_content_ = true;
239   base::MessageLoop::current()->PostTask(
240       FROM_HERE,
241       base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr()));
242 }
243
244 void WebNotificationTray::OnStatusIconClicked() {
245   // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura.
246   gfx::Screen* screen = gfx::Screen::GetNativeScreen();
247   mouse_click_point_ = screen->GetCursorScreenPoint();
248   message_center_tray_->ToggleMessageCenterBubble();
249 }
250
251 void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) {
252   if (command_id == kToggleQuietMode) {
253     bool in_quiet_mode = message_center()->IsQuietMode();
254     message_center()->SetQuietMode(!in_quiet_mode);
255     return;
256   }
257   base::TimeDelta expires_in = command_id == kEnableQuietModeDay
258                                    ? base::TimeDelta::FromDays(1)
259                                    : base::TimeDelta::FromHours(1);
260   message_center()->EnterQuietModeWithExpire(expires_in);
261 }
262
263 void WebNotificationTray::UpdateStatusIcon() {
264   if (!should_update_tray_content_)
265     return;
266   should_update_tray_content_ = false;
267
268   int unread_notifications = message_center()->UnreadNotificationCount();
269
270   base::string16 tool_tip;
271   if (unread_notifications > 0) {
272     base::string16 str_unread_count = base::FormatNumber(unread_notifications);
273     tool_tip = l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP_UNREAD,
274                                           str_unread_count);
275   } else {
276     tool_tip = l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_TOOLTIP);
277   }
278
279   if (message_center()->GetVisibleNotifications().empty()) {
280     DestroyStatusIcon();
281     return;
282   }
283
284   gfx::ImageSkia* icon_image = GetIcon(
285       unread_notifications,
286       message_center()->IsQuietMode());
287
288   if (status_icon_) {
289     status_icon_->SetImage(*icon_image);
290     status_icon_->SetToolTip(tool_tip);
291     return;
292   }
293
294   CreateStatusIcon(*icon_image, tool_tip);
295 }
296
297 void WebNotificationTray::SendHideMessageCenter() {
298   message_center_tray_->HideMessageCenterBubble();
299 }
300
301 void WebNotificationTray::MarkMessageCenterHidden() {
302   if (message_center_delegate_) {
303     message_center_tray_->MarkMessageCenterHidden();
304     message_center_delegate_ = NULL;
305   }
306 }
307
308 PositionInfo WebNotificationTray::GetPositionInfo() {
309   PositionInfo pos_info;
310
311   gfx::Screen* screen = gfx::Screen::GetNativeScreen();
312   gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
313   work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
314
315   gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_);
316
317   pos_info.taskbar_alignment = internal::GetTaskbarAlignment();
318
319   // We assume the taskbar is either at the top or at the bottom if we are not
320   // able to find it.
321   if (pos_info.taskbar_alignment == ALIGNMENT_NONE) {
322     if (mouse_click_point_.y() > corner.y())
323       pos_info.taskbar_alignment = ALIGNMENT_TOP;
324     else
325       pos_info.taskbar_alignment = ALIGNMENT_BOTTOM;
326   }
327
328   pos_info.message_center_alignment =
329       internal::GetAnchorAlignment(work_area, corner);
330
331   pos_info.inital_anchor_point = corner;
332   pos_info.max_height = work_area.height();
333
334   if (work_area.Contains(mouse_click_point_)) {
335     // Message center is in the work area. So position it few pixels above the
336     // mouse click point if alignemnt is towards bottom and few pixels below if
337     // alignment is towards top.
338     pos_info.inital_anchor_point.set_y(
339         mouse_click_point_.y() +
340         (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -kMouseOffset
341                                                               : kMouseOffset));
342
343     // Subtract the distance between mouse click point and the closest
344     // (insetted) edge from the max height to show the message center within the
345     // (insetted) work area bounds. Also subtract the offset from the mouse
346     // click point we added earlier.
347     pos_info.max_height -=
348         std::abs(mouse_click_point_.y() - corner.y()) + kMouseOffset;
349   }
350   return pos_info;
351 }
352
353 MessageCenterTray* WebNotificationTray::GetMessageCenterTray() {
354   return message_center_tray_.get();
355 }
356
357 void WebNotificationTray::CreateStatusIcon(const gfx::ImageSkia& image,
358                                            const base::string16& tool_tip) {
359   if (status_icon_)
360     return;
361
362   StatusTray* status_tray = g_browser_process->status_tray();
363   if (!status_tray)
364     return;
365
366   status_icon_ = status_tray->CreateStatusIcon(
367       StatusTray::NOTIFICATION_TRAY_ICON, image, tool_tip);
368   if (!status_icon_)
369     return;
370
371   status_icon_->AddObserver(this);
372   AddQuietModeMenu(status_icon_);
373 }
374
375 void WebNotificationTray::DestroyStatusIcon() {
376   if (!status_icon_)
377     return;
378
379   status_icon_->RemoveObserver(this);
380   StatusTray* status_tray = g_browser_process->status_tray();
381   if (status_tray)
382     status_tray->RemoveStatusIcon(status_icon_);
383   status_icon_menu_ = NULL;
384   status_icon_ = NULL;
385 }
386
387 void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) {
388   DCHECK(status_icon);
389
390   scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
391   menu->AddCheckItem(kToggleQuietMode,
392                      l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE));
393   menu->SetCommandIdChecked(kToggleQuietMode, message_center()->IsQuietMode());
394   menu->AddItem(kEnableQuietModeHour,
395                 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1HOUR));
396   menu->AddItem(kEnableQuietModeDay,
397                 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1DAY));
398
399   status_icon_menu_ = menu.get();
400   status_icon->SetContextMenu(menu.Pass());
401 }
402
403 MessageCenterWidgetDelegate*
404 WebNotificationTray::GetMessageCenterWidgetDelegateForTest() {
405   return message_center_delegate_;
406 }
407
408 }  // namespace message_center