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