web_notification_tray.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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 } 221 222 // See the comments in ash/system/web_notification/web_notification_tray.cc 223 // for why PostTask. 224 should_update_tray_content_ = true; 225 base::MessageLoop::current()->PostTask( 226 FROM_HERE, 227 base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr())); 228} 229 230void WebNotificationTray::OnStatusIconClicked() { 231 // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura. 232 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 233 mouse_click_point_ = screen->GetCursorScreenPoint(); 234 message_center_tray_->ToggleMessageCenterBubble(); 235} 236 237void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) { 238 if (command_id == kToggleQuietMode) { 239 bool in_quiet_mode = message_center()->IsQuietMode(); 240 message_center()->SetQuietMode(!in_quiet_mode); 241 return; 242 } 243 base::TimeDelta expires_in = command_id == kEnableQuietModeDay 244 ? base::TimeDelta::FromDays(1) 245 : base::TimeDelta::FromHours(1); 246 message_center()->EnterQuietModeWithExpire(expires_in); 247} 248 249void WebNotificationTray::UpdateStatusIcon() { 250 if (!should_update_tray_content_) 251 return; 252 should_update_tray_content_ = false; 253 254 int unread_notifications = message_center()->UnreadNotificationCount(); 255 256 base::string16 tool_tip; 257 if (unread_notifications > 0) { 258 base::string16 str_unread_count = base::FormatNumber(unread_notifications); 259 tool_tip = l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP_UNREAD, 260 str_unread_count); 261 } else { 262 tool_tip = l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_TOOLTIP); 263 } 264 265 gfx::ImageSkia* icon_image = GetIcon( 266 unread_notifications, 267 message_center()->IsQuietMode()); 268 269 if (status_icon_) { 270 status_icon_->SetImage(*icon_image); 271 status_icon_->SetToolTip(tool_tip); 272 return; 273 } 274 275 CreateStatusIcon(*icon_image, tool_tip); 276} 277 278void WebNotificationTray::SendHideMessageCenter() { 279 message_center_tray_->HideMessageCenterBubble(); 280} 281 282void WebNotificationTray::MarkMessageCenterHidden() { 283 if (message_center_delegate_) { 284 message_center_tray_->MarkMessageCenterHidden(); 285 message_center_delegate_ = NULL; 286 } 287} 288 289PositionInfo WebNotificationTray::GetPositionInfo() { 290 PositionInfo pos_info; 291 292 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 293 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 294 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding); 295 296 gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_); 297 298 pos_info.taskbar_alignment = internal::GetTaskbarAlignment(); 299 300 // We assume the taskbar is either at the top or at the bottom if we are not 301 // able to find it. 302 if (pos_info.taskbar_alignment == ALIGNMENT_NONE) { 303 if (mouse_click_point_.y() > corner.y()) 304 pos_info.taskbar_alignment = ALIGNMENT_TOP; 305 else 306 pos_info.taskbar_alignment = ALIGNMENT_BOTTOM; 307 } 308 309 pos_info.message_center_alignment = 310 internal::GetAnchorAlignment(work_area, corner); 311 312 pos_info.inital_anchor_point = corner; 313 pos_info.max_height = work_area.height(); 314 315 if (work_area.Contains(mouse_click_point_)) { 316 // Message center is in the work area. So position it few pixels above the 317 // mouse click point if alignemnt is towards bottom and few pixels below if 318 // alignment is towards top. 319 pos_info.inital_anchor_point.set_y( 320 mouse_click_point_.y() + 321 (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -kMouseOffset 322 : kMouseOffset)); 323 324 // Subtract the distance between mouse click point and the closest 325 // (insetted) edge from the max height to show the message center within the 326 // (insetted) work area bounds. Also subtract the offset from the mouse 327 // click point we added earlier. 328 pos_info.max_height -= 329 std::abs(mouse_click_point_.y() - corner.y()) + kMouseOffset; 330 } 331 return pos_info; 332} 333 334MessageCenterTray* WebNotificationTray::GetMessageCenterTray() { 335 return message_center_tray_.get(); 336} 337 338void WebNotificationTray::CreateStatusIcon(const gfx::ImageSkia& image, 339 const base::string16& tool_tip) { 340 if (status_icon_) 341 return; 342 343 StatusTray* status_tray = g_browser_process->status_tray(); 344 if (!status_tray) 345 return; 346 347 status_icon_ = status_tray->CreateStatusIcon( 348 StatusTray::NOTIFICATION_TRAY_ICON, image, tool_tip); 349 if (!status_icon_) 350 return; 351 352 status_icon_->AddObserver(this); 353 AddQuietModeMenu(status_icon_); 354} 355 356void WebNotificationTray::DestroyStatusIcon() { 357 if (!status_icon_) 358 return; 359 360 status_icon_->RemoveObserver(this); 361 StatusTray* status_tray = g_browser_process->status_tray(); 362 if (status_tray) 363 status_tray->RemoveStatusIcon(status_icon_); 364 status_icon_menu_ = NULL; 365 status_icon_ = NULL; 366} 367 368void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) { 369 DCHECK(status_icon); 370 371 scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this)); 372 menu->AddCheckItem(kToggleQuietMode, 373 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE)); 374 menu->SetCommandIdChecked(kToggleQuietMode, message_center()->IsQuietMode()); 375 menu->AddItem(kEnableQuietModeHour, 376 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1HOUR)); 377 menu->AddItem(kEnableQuietModeDay, 378 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1DAY)); 379 380 status_icon_menu_ = menu.get(); 381 status_icon->SetContextMenu(menu.Pass()); 382} 383 384MessageCenterWidgetDelegate* 385WebNotificationTray::GetMessageCenterWidgetDelegateForTest() { 386 return message_center_delegate_; 387} 388 389} // namespace message_center 390