web_notification_tray.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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 32namespace { 33 34// Tray constants 35const int kScreenEdgePadding = 2; 36 37// Number of pixels the message center is offset from the mouse. 38const int kMouseOffset = 5; 39 40// Menu commands 41const int kToggleQuietMode = 0; 42const int kEnableQuietModeHour = 1; 43const int kEnableQuietModeDay = 2; 44 45gfx::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 63namespace message_center { 64 65namespace internal { 66 67// Gets the position of the taskbar from the work area bounds. Returns 68// ALIGNMENT_NONE if position cannot be found. 69Alignment 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 93gfx::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. 111Alignment 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 125MessageCenterTrayDelegate* CreateMessageCenterTray() { 126 return new WebNotificationTray(g_browser_process->local_state()); 127} 128 129WebNotificationTray::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 150WebNotificationTray::~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 158message_center::MessageCenter* WebNotificationTray::message_center() { 159 return message_center_tray_->message_center(); 160} 161 162bool WebNotificationTray::ShowPopups() { 163 popup_collection_->DoUpdateIfPossible(); 164 return true; 165} 166 167void WebNotificationTray::HidePopups() { 168 DCHECK(popup_collection_.get()); 169 popup_collection_->MarkAllPopupsShown(); 170} 171 172bool 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 182void WebNotificationTray::HideMessageCenter() { 183 if (message_center_delegate_) { 184 views::Widget* widget = message_center_delegate_->GetWidget(); 185 if (widget) 186 widget->Close(); 187 } 188} 189 190bool 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 204bool 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 210void 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 } else if (message_center()->NotificationCount() == 0) { 221 // If there's no existing status icon and we still don't have any 222 // notifications to display, nothing needs to be done. 223 return; 224 } 225 226 // See the comments in ash/system/web_notification/web_notification_tray.cc 227 // for why PostTask. 228 should_update_tray_content_ = true; 229 base::MessageLoop::current()->PostTask( 230 FROM_HERE, 231 base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr())); 232} 233 234void WebNotificationTray::OnStatusIconClicked() { 235 // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura. 236 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 237 mouse_click_point_ = screen->GetCursorScreenPoint(); 238 message_center_tray_->ToggleMessageCenterBubble(); 239} 240 241void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) { 242 if (command_id == kToggleQuietMode) { 243 bool in_quiet_mode = message_center()->IsQuietMode(); 244 message_center()->SetQuietMode(!in_quiet_mode); 245 return; 246 } 247 base::TimeDelta expires_in = command_id == kEnableQuietModeDay 248 ? base::TimeDelta::FromDays(1) 249 : base::TimeDelta::FromHours(1); 250 message_center()->EnterQuietModeWithExpire(expires_in); 251} 252 253void WebNotificationTray::UpdateStatusIcon() { 254 if (!should_update_tray_content_) 255 return; 256 should_update_tray_content_ = false; 257 258 int unread_notifications = message_center()->UnreadNotificationCount(); 259 260 base::string16 tool_tip; 261 if (unread_notifications > 0) { 262 base::string16 str_unread_count = base::FormatNumber(unread_notifications); 263 tool_tip = l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP_UNREAD, 264 str_unread_count); 265 } else { 266 tool_tip = l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_TOOLTIP); 267 } 268 269 gfx::ImageSkia* icon_image = GetIcon( 270 unread_notifications, 271 message_center()->IsQuietMode()); 272 273 if (status_icon_) { 274 status_icon_->SetImage(*icon_image); 275 status_icon_->SetToolTip(tool_tip); 276 return; 277 } 278 279 CreateStatusIcon(*icon_image, tool_tip); 280} 281 282void WebNotificationTray::SendHideMessageCenter() { 283 message_center_tray_->HideMessageCenterBubble(); 284} 285 286void WebNotificationTray::MarkMessageCenterHidden() { 287 if (message_center_delegate_) { 288 message_center_tray_->MarkMessageCenterHidden(); 289 message_center_delegate_ = NULL; 290 } 291} 292 293PositionInfo WebNotificationTray::GetPositionInfo() { 294 PositionInfo pos_info; 295 296 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 297 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 298 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding); 299 300 gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_); 301 302 pos_info.taskbar_alignment = internal::GetTaskbarAlignment(); 303 304 // We assume the taskbar is either at the top or at the bottom if we are not 305 // able to find it. 306 if (pos_info.taskbar_alignment == ALIGNMENT_NONE) { 307 if (mouse_click_point_.y() > corner.y()) 308 pos_info.taskbar_alignment = ALIGNMENT_TOP; 309 else 310 pos_info.taskbar_alignment = ALIGNMENT_BOTTOM; 311 } 312 313 pos_info.message_center_alignment = 314 internal::GetAnchorAlignment(work_area, corner); 315 316 pos_info.inital_anchor_point = corner; 317 pos_info.max_height = work_area.height(); 318 319 if (work_area.Contains(mouse_click_point_)) { 320 // Message center is in the work area. So position it few pixels above the 321 // mouse click point if alignemnt is towards bottom and few pixels below if 322 // alignment is towards top. 323 pos_info.inital_anchor_point.set_y( 324 mouse_click_point_.y() + 325 (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -kMouseOffset 326 : kMouseOffset)); 327 328 // Subtract the distance between mouse click point and the closest 329 // (insetted) edge from the max height to show the message center within the 330 // (insetted) work area bounds. Also subtract the offset from the mouse 331 // click point we added earlier. 332 pos_info.max_height -= 333 std::abs(mouse_click_point_.y() - corner.y()) + kMouseOffset; 334 } 335 return pos_info; 336} 337 338MessageCenterTray* WebNotificationTray::GetMessageCenterTray() { 339 return message_center_tray_.get(); 340} 341 342void WebNotificationTray::CreateStatusIcon(const gfx::ImageSkia& image, 343 const base::string16& tool_tip) { 344 if (status_icon_) 345 return; 346 347 StatusTray* status_tray = g_browser_process->status_tray(); 348 if (!status_tray) 349 return; 350 351 status_icon_ = status_tray->CreateStatusIcon( 352 StatusTray::NOTIFICATION_TRAY_ICON, image, tool_tip); 353 if (!status_icon_) 354 return; 355 356 status_icon_->AddObserver(this); 357 AddQuietModeMenu(status_icon_); 358} 359 360void WebNotificationTray::DestroyStatusIcon() { 361 if (!status_icon_) 362 return; 363 364 status_icon_->RemoveObserver(this); 365 StatusTray* status_tray = g_browser_process->status_tray(); 366 if (status_tray) 367 status_tray->RemoveStatusIcon(status_icon_); 368 status_icon_menu_ = NULL; 369 status_icon_ = NULL; 370} 371 372void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) { 373 DCHECK(status_icon); 374 375 scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this)); 376 menu->AddCheckItem(kToggleQuietMode, 377 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE)); 378 menu->SetCommandIdChecked(kToggleQuietMode, message_center()->IsQuietMode()); 379 menu->AddItem(kEnableQuietModeHour, 380 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1HOUR)); 381 menu->AddItem(kEnableQuietModeDay, 382 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1DAY)); 383 384 status_icon_menu_ = menu.get(); 385 status_icon->SetContextMenu(menu.Pass()); 386} 387 388MessageCenterWidgetDelegate* 389WebNotificationTray::GetMessageCenterWidgetDelegateForTest() { 390 return message_center_delegate_; 391} 392 393} // namespace message_center 394