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