Infobar material design refresh: layout
[chromium-blink-merge.git] / chrome / browser / ui / views / message_center / web_notification_tray.cc
blob300680784dd89223e9f9ba28d8f75ddd9b36060b
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/location.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/strings/string16.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/status_icons/status_icon.h"
16 #include "chrome/browser/status_icons/status_icon_menu_model.h"
17 #include "chrome/browser/status_icons/status_tray.h"
18 #include "chrome/common/pref_names.h"
19 #include "chrome/grit/chromium_strings.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "content/public/browser/notification_service.h"
22 #include "grit/theme_resources.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/gfx/canvas.h"
26 #include "ui/gfx/geometry/rect.h"
27 #include "ui/gfx/geometry/size.h"
28 #include "ui/gfx/image/image_skia_operations.h"
29 #include "ui/gfx/screen.h"
30 #include "ui/message_center/message_center_tray.h"
31 #include "ui/message_center/message_center_tray_delegate.h"
32 #include "ui/message_center/views/desktop_popup_alignment_delegate.h"
33 #include "ui/message_center/views/message_popup_collection.h"
34 #include "ui/strings/grit/ui_strings.h"
35 #include "ui/views/widget/widget.h"
37 #if defined(OS_LINUX)
38 #include "base/environment.h"
39 #include "base/nix/xdg_util.h"
40 #endif
42 namespace {
44 // Tray constants
45 const int kScreenEdgePadding = 2;
47 // Number of pixels the message center is offset from the mouse.
48 const int kMouseOffset = 5;
50 // Menu commands
51 const int kToggleQuietMode = 0;
52 const int kEnableQuietModeHour = 1;
53 const int kEnableQuietModeDay = 2;
55 gfx::ImageSkia* GetIcon(int unread_count, bool is_quiet_mode) {
56 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
57 int resource_id = IDR_NOTIFICATION_TRAY_EMPTY;
59 if (unread_count) {
60 if (is_quiet_mode)
61 resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_ATTENTION;
62 else
63 resource_id = IDR_NOTIFICATION_TRAY_ATTENTION;
64 } else if (is_quiet_mode) {
65 resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_EMPTY;
68 return rb.GetImageSkiaNamed(resource_id);
71 bool CanDestroyStatusIcon() {
72 #if defined(OS_LINUX)
73 // Avoid creating multiple system tray icons on KDE4 and newer versions of KDE
74 // because the OS does not support removing system tray icons.
75 // TODO(pkotwicz): This is a hack for the sake of M40. Fix this properly.
76 scoped_ptr<base::Environment> env(base::Environment::Create());
77 base::nix::DesktopEnvironment desktop_environment =
78 base::nix::GetDesktopEnvironment(env.get());
79 return desktop_environment != base::nix::DESKTOP_ENVIRONMENT_KDE4;
80 #else
81 return true;
82 #endif
85 } // namespace
87 namespace message_center {
89 namespace internal {
91 // Gets the position of the taskbar from the work area bounds. Returns
92 // ALIGNMENT_NONE if position cannot be found.
93 Alignment GetTaskbarAlignment() {
94 gfx::Screen* screen = gfx::Screen::GetNativeScreen();
95 // TODO(dewittj): It's possible GetPrimaryDisplay is wrong.
96 gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds();
97 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
98 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
100 // Comparing the work area to the screen bounds gives us the location of the
101 // taskbar. If the work area is exactly the same as the screen bounds,
102 // we are unable to locate the taskbar so we say we don't know it's alignment.
103 if (work_area.height() < screen_bounds.height()) {
104 if (work_area.y() > screen_bounds.y())
105 return ALIGNMENT_TOP;
106 return ALIGNMENT_BOTTOM;
108 if (work_area.width() < screen_bounds.width()) {
109 if (work_area.x() > screen_bounds.x())
110 return ALIGNMENT_LEFT;
111 return ALIGNMENT_RIGHT;
114 return ALIGNMENT_NONE;
117 gfx::Point GetClosestCorner(const gfx::Rect& rect, const gfx::Point& query) {
118 gfx::Point center_point = rect.CenterPoint();
119 gfx::Point rv;
121 if (query.x() > center_point.x())
122 rv.set_x(rect.right());
123 else
124 rv.set_x(rect.x());
126 if (query.y() > center_point.y())
127 rv.set_y(rect.bottom());
128 else
129 rv.set_y(rect.y());
131 return rv;
134 // Gets the corner of the screen where the message center should pop up.
135 Alignment GetAnchorAlignment(const gfx::Rect& work_area, gfx::Point corner) {
136 gfx::Point center = work_area.CenterPoint();
138 Alignment anchor_alignment =
139 center.y() > corner.y() ? ALIGNMENT_TOP : ALIGNMENT_BOTTOM;
140 anchor_alignment =
141 (Alignment)(anchor_alignment |
142 (center.x() > corner.x() ? ALIGNMENT_LEFT : ALIGNMENT_RIGHT));
144 return anchor_alignment;
147 } // namespace internal
149 MessageCenterTrayDelegate* CreateMessageCenterTray() {
150 return new WebNotificationTray(g_browser_process->local_state());
153 WebNotificationTray::WebNotificationTray(PrefService* local_state)
154 : message_center_delegate_(NULL),
155 status_icon_(NULL),
156 status_icon_menu_(NULL),
157 should_update_tray_content_(true) {
158 message_center_tray_.reset(
159 new MessageCenterTray(this, g_browser_process->message_center()));
160 last_quiet_mode_state_ = message_center()->IsQuietMode();
161 alignment_delegate_.reset(new message_center::DesktopPopupAlignmentDelegate);
162 popup_collection_.reset(new message_center::MessagePopupCollection(
163 NULL, message_center(), message_center_tray_.get(),
164 alignment_delegate_.get()));
166 #if defined(OS_WIN)
167 // |local_state| can be NULL in tests.
168 if (local_state) {
169 did_force_tray_visible_.reset(new BooleanPrefMember());
170 did_force_tray_visible_->Init(prefs::kMessageCenterForcedOnTaskbar,
171 local_state);
173 #endif
174 title_ = l10n_util::GetStringFUTF16(
175 IDS_MESSAGE_CENTER_FOOTER_WITH_PRODUCT_TITLE,
176 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
179 WebNotificationTray::~WebNotificationTray() {
180 // Reset this early so that delegated events during destruction don't cause
181 // problems.
182 popup_collection_.reset();
183 message_center_tray_.reset();
184 DestroyStatusIcon();
187 message_center::MessageCenter* WebNotificationTray::message_center() {
188 return message_center_tray_->message_center();
191 bool WebNotificationTray::ShowPopups() {
192 alignment_delegate_->StartObserving(gfx::Screen::GetNativeScreen());
193 popup_collection_->DoUpdateIfPossible();
194 return true;
197 void WebNotificationTray::HidePopups() {
198 DCHECK(popup_collection_.get());
199 popup_collection_->MarkAllPopupsShown();
202 bool WebNotificationTray::ShowMessageCenter() {
203 message_center_delegate_ =
204 new MessageCenterWidgetDelegate(this,
205 message_center_tray_.get(),
206 false, // settings initally invisible
207 GetPositionInfo(),
208 title_);
210 return true;
213 void WebNotificationTray::HideMessageCenter() {
214 if (message_center_delegate_) {
215 views::Widget* widget = message_center_delegate_->GetWidget();
216 if (widget)
217 widget->Close();
221 bool WebNotificationTray::ShowNotifierSettings() {
222 if (message_center_delegate_) {
223 message_center_delegate_->SetSettingsVisible(true);
224 return true;
226 message_center_delegate_ =
227 new MessageCenterWidgetDelegate(this,
228 message_center_tray_.get(),
229 true, // settings initally visible
230 GetPositionInfo(),
231 title_);
233 return true;
236 bool WebNotificationTray::IsContextMenuEnabled() const {
237 // It can always return true because the notifications are invisible if
238 // the context menu shouldn't be enabled, such as in the lock screen.
239 return true;
242 void WebNotificationTray::OnMessageCenterTrayChanged() {
243 if (status_icon_) {
244 bool quiet_mode_state = message_center()->IsQuietMode();
245 if (last_quiet_mode_state_ != quiet_mode_state) {
246 last_quiet_mode_state_ = quiet_mode_state;
248 // Quiet mode has changed, update the quiet mode menu.
249 status_icon_menu_->SetCommandIdChecked(kToggleQuietMode,
250 quiet_mode_state);
252 } else if (message_center()->NotificationCount() == 0) {
253 // If there's no existing status icon and we still don't have any
254 // notifications to display, nothing needs to be done.
255 return;
258 // See the comments in ash/system/web_notification/web_notification_tray.cc
259 // for why PostTask.
260 should_update_tray_content_ = true;
261 base::ThreadTaskRunnerHandle::Get()->PostTask(
262 FROM_HERE,
263 base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr()));
266 void WebNotificationTray::OnStatusIconClicked() {
267 // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura.
268 gfx::Screen* screen = gfx::Screen::GetNativeScreen();
269 mouse_click_point_ = screen->GetCursorScreenPoint();
270 message_center_tray_->ToggleMessageCenterBubble();
273 void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) {
274 if (command_id == kToggleQuietMode) {
275 bool in_quiet_mode = message_center()->IsQuietMode();
276 message_center()->SetQuietMode(!in_quiet_mode);
277 return;
279 base::TimeDelta expires_in = command_id == kEnableQuietModeDay
280 ? base::TimeDelta::FromDays(1)
281 : base::TimeDelta::FromHours(1);
282 message_center()->EnterQuietModeWithExpire(expires_in);
285 void WebNotificationTray::UpdateStatusIcon() {
286 if (!should_update_tray_content_)
287 return;
288 should_update_tray_content_ = false;
290 int unread_notifications = message_center()->UnreadNotificationCount();
292 base::string16 tool_tip;
293 if (unread_notifications > 0) {
294 base::string16 str_unread_count = base::FormatNumber(unread_notifications);
295 tool_tip = l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP_UNREAD,
296 str_unread_count);
297 } else {
298 tool_tip = l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_TOOLTIP);
301 if (message_center()->GetVisibleNotifications().empty() &&
302 CanDestroyStatusIcon()) {
303 DestroyStatusIcon();
304 return;
307 gfx::ImageSkia* icon_image = GetIcon(
308 unread_notifications,
309 message_center()->IsQuietMode());
311 if (status_icon_) {
312 status_icon_->SetImage(*icon_image);
313 status_icon_->SetToolTip(tool_tip);
314 return;
317 CreateStatusIcon(*icon_image, tool_tip);
320 void WebNotificationTray::SendHideMessageCenter() {
321 message_center_tray_->HideMessageCenterBubble();
324 void WebNotificationTray::MarkMessageCenterHidden() {
325 if (message_center_delegate_) {
326 message_center_tray_->MarkMessageCenterHidden();
327 message_center_delegate_ = NULL;
331 PositionInfo WebNotificationTray::GetPositionInfo() {
332 PositionInfo pos_info;
334 gfx::Screen* screen = gfx::Screen::GetNativeScreen();
335 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area();
336 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding);
338 gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_);
340 pos_info.taskbar_alignment = internal::GetTaskbarAlignment();
342 // We assume the taskbar is either at the top or at the bottom if we are not
343 // able to find it.
344 if (pos_info.taskbar_alignment == ALIGNMENT_NONE) {
345 if (mouse_click_point_.y() > corner.y())
346 pos_info.taskbar_alignment = ALIGNMENT_TOP;
347 else
348 pos_info.taskbar_alignment = ALIGNMENT_BOTTOM;
351 pos_info.message_center_alignment =
352 internal::GetAnchorAlignment(work_area, corner);
354 pos_info.inital_anchor_point = corner;
355 pos_info.max_height = work_area.height();
357 if (work_area.Contains(mouse_click_point_)) {
358 // Message center is in the work area. So position it few pixels above the
359 // mouse click point if alignemnt is towards bottom and few pixels below if
360 // alignment is towards top.
361 pos_info.inital_anchor_point.set_y(
362 mouse_click_point_.y() +
363 (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -kMouseOffset
364 : kMouseOffset));
366 // Subtract the distance between mouse click point and the closest
367 // (insetted) edge from the max height to show the message center within the
368 // (insetted) work area bounds. Also subtract the offset from the mouse
369 // click point we added earlier.
370 pos_info.max_height -=
371 std::abs(mouse_click_point_.y() - corner.y()) + kMouseOffset;
373 return pos_info;
376 MessageCenterTray* WebNotificationTray::GetMessageCenterTray() {
377 return message_center_tray_.get();
380 void WebNotificationTray::CreateStatusIcon(const gfx::ImageSkia& image,
381 const base::string16& tool_tip) {
382 if (status_icon_)
383 return;
385 StatusTray* status_tray = g_browser_process->status_tray();
386 if (!status_tray)
387 return;
389 status_icon_ = status_tray->CreateStatusIcon(
390 StatusTray::NOTIFICATION_TRAY_ICON, image, tool_tip);
391 if (!status_icon_)
392 return;
394 status_icon_->AddObserver(this);
395 AddQuietModeMenu(status_icon_);
398 void WebNotificationTray::DestroyStatusIcon() {
399 if (!status_icon_)
400 return;
402 status_icon_->RemoveObserver(this);
403 StatusTray* status_tray = g_browser_process->status_tray();
404 if (status_tray)
405 status_tray->RemoveStatusIcon(status_icon_);
406 status_icon_menu_ = NULL;
407 status_icon_ = NULL;
410 void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) {
411 DCHECK(status_icon);
413 scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this));
414 menu->AddCheckItem(kToggleQuietMode,
415 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE));
416 menu->SetCommandIdChecked(kToggleQuietMode, message_center()->IsQuietMode());
417 menu->AddItem(kEnableQuietModeHour,
418 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1HOUR));
419 menu->AddItem(kEnableQuietModeDay,
420 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1DAY));
422 status_icon_menu_ = menu.get();
423 status_icon->SetContextMenu(menu.Pass());
426 MessageCenterWidgetDelegate*
427 WebNotificationTray::GetMessageCenterWidgetDelegateForTest() {
428 return message_center_delegate_;
431 } // namespace message_center