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