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