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/generated_resources.h" 19 #include "grit/theme_resources.h" 20 #include "grit/ui_strings.h" 21 #include "ui/base/l10n/l10n_util.h" 22 #include "ui/base/resource/resource_bundle.h" 23 #include "ui/gfx/canvas.h" 24 #include "ui/gfx/image/image_skia_operations.h" 25 #include "ui/gfx/rect.h" 26 #include "ui/gfx/screen.h" 27 #include "ui/gfx/size.h" 28 #include "ui/message_center/message_center_tray.h" 29 #include "ui/message_center/message_center_tray_delegate.h" 30 #include "ui/message_center/views/message_popup_collection.h" 31 #include "ui/views/widget/widget.h" 32 33 namespace { 34 35 // Tray constants 36 const int kScreenEdgePadding = 2; 37 38 // Number of pixels the message center is offset from the mouse. 39 const int kMouseOffset = 5; 40 41 // Menu commands 42 const int kToggleQuietMode = 0; 43 const int kEnableQuietModeHour = 1; 44 const int kEnableQuietModeDay = 2; 45 46 gfx::ImageSkia* GetIcon(int unread_count, bool is_quiet_mode) { 47 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 48 int resource_id = IDR_NOTIFICATION_TRAY_EMPTY; 49 50 if (unread_count) { 51 if (is_quiet_mode) 52 resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_ATTENTION; 53 else 54 resource_id = IDR_NOTIFICATION_TRAY_ATTENTION; 55 } else if (is_quiet_mode) { 56 resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_EMPTY; 57 } 58 59 return rb.GetImageSkiaNamed(resource_id); 60 } 61 62 } // namespace 63 64 namespace message_center { 65 66 namespace internal { 67 68 // Gets the position of the taskbar from the work area bounds. Returns 69 // ALIGNMENT_NONE if position cannot be found. 70 Alignment GetTaskbarAlignment() { 71 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 72 // TODO(dewittj): It's possible GetPrimaryDisplay is wrong. 73 gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds(); 74 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 75 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding); 76 77 // Comparing the work area to the screen bounds gives us the location of the 78 // taskbar. If the work area is exactly the same as the screen bounds, 79 // we are unable to locate the taskbar so we say we don't know it's alignment. 80 if (work_area.height() < screen_bounds.height()) { 81 if (work_area.y() > screen_bounds.y()) 82 return ALIGNMENT_TOP; 83 return ALIGNMENT_BOTTOM; 84 } 85 if (work_area.width() < screen_bounds.width()) { 86 if (work_area.x() > screen_bounds.x()) 87 return ALIGNMENT_LEFT; 88 return ALIGNMENT_RIGHT; 89 } 90 91 return ALIGNMENT_NONE; 92 } 93 94 gfx::Point GetClosestCorner(const gfx::Rect& rect, const gfx::Point& query) { 95 gfx::Point center_point = rect.CenterPoint(); 96 gfx::Point rv; 97 98 if (query.x() > center_point.x()) 99 rv.set_x(rect.right()); 100 else 101 rv.set_x(rect.x()); 102 103 if (query.y() > center_point.y()) 104 rv.set_y(rect.bottom()); 105 else 106 rv.set_y(rect.y()); 107 108 return rv; 109 } 110 111 // Gets the corner of the screen where the message center should pop up. 112 Alignment GetAnchorAlignment(const gfx::Rect& work_area, gfx::Point corner) { 113 gfx::Point center = work_area.CenterPoint(); 114 115 Alignment anchor_alignment = 116 center.y() > corner.y() ? ALIGNMENT_TOP : ALIGNMENT_BOTTOM; 117 anchor_alignment = 118 (Alignment)(anchor_alignment | 119 (center.x() > corner.x() ? ALIGNMENT_LEFT : ALIGNMENT_RIGHT)); 120 121 return anchor_alignment; 122 } 123 124 } // namespace internal 125 126 MessageCenterTrayDelegate* CreateMessageCenterTray() { 127 return new WebNotificationTray(g_browser_process->local_state()); 128 } 129 130 WebNotificationTray::WebNotificationTray(PrefService* local_state) 131 : message_center_delegate_(NULL), 132 status_icon_(NULL), 133 status_icon_menu_(NULL), 134 should_update_tray_content_(true) { 135 message_center_tray_.reset( 136 new MessageCenterTray(this, g_browser_process->message_center())); 137 last_quiet_mode_state_ = message_center()->IsQuietMode(); 138 popup_collection_.reset(new message_center::MessagePopupCollection( 139 NULL, message_center(), message_center_tray_.get(), false)); 140 141 #if defined(OS_WIN) 142 // |local_state| can be NULL in tests. 143 if (local_state) { 144 did_force_tray_visible_.reset(new BooleanPrefMember()); 145 did_force_tray_visible_->Init(prefs::kMessageCenterForcedOnTaskbar, 146 local_state); 147 } 148 #endif 149 title_ = l10n_util::GetStringFUTF16( 150 IDS_MESSAGE_CENTER_FOOTER_WITH_PRODUCT_TITLE, 151 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); 152 } 153 154 WebNotificationTray::~WebNotificationTray() { 155 // Reset this early so that delegated events during destruction don't cause 156 // problems. 157 popup_collection_.reset(); 158 message_center_tray_.reset(); 159 DestroyStatusIcon(); 160 } 161 162 message_center::MessageCenter* WebNotificationTray::message_center() { 163 return message_center_tray_->message_center(); 164 } 165 166 bool WebNotificationTray::ShowPopups() { 167 popup_collection_->DoUpdateIfPossible(); 168 return true; 169 } 170 171 void WebNotificationTray::HidePopups() { 172 DCHECK(popup_collection_.get()); 173 popup_collection_->MarkAllPopupsShown(); 174 } 175 176 bool WebNotificationTray::ShowMessageCenter() { 177 message_center_delegate_ = 178 new MessageCenterWidgetDelegate(this, 179 message_center_tray_.get(), 180 false, // settings initally invisible 181 GetPositionInfo(), 182 title_); 183 184 return true; 185 } 186 187 void WebNotificationTray::HideMessageCenter() { 188 if (message_center_delegate_) { 189 views::Widget* widget = message_center_delegate_->GetWidget(); 190 if (widget) 191 widget->Close(); 192 } 193 } 194 195 bool WebNotificationTray::ShowNotifierSettings() { 196 if (message_center_delegate_) { 197 message_center_delegate_->SetSettingsVisible(true); 198 return true; 199 } 200 message_center_delegate_ = 201 new MessageCenterWidgetDelegate(this, 202 message_center_tray_.get(), 203 true, // settings initally visible 204 GetPositionInfo(), 205 title_); 206 207 return true; 208 } 209 210 bool WebNotificationTray::IsContextMenuEnabled() const { 211 // It can always return true because the notifications are invisible if 212 // the context menu shouldn't be enabled, such as in the lock screen. 213 return true; 214 } 215 216 void WebNotificationTray::OnMessageCenterTrayChanged() { 217 if (status_icon_) { 218 bool quiet_mode_state = message_center()->IsQuietMode(); 219 if (last_quiet_mode_state_ != quiet_mode_state) { 220 last_quiet_mode_state_ = quiet_mode_state; 221 222 // Quiet mode has changed, update the quiet mode menu. 223 status_icon_menu_->SetCommandIdChecked(kToggleQuietMode, 224 quiet_mode_state); 225 } 226 } else if (message_center()->NotificationCount() == 0) { 227 // If there's no existing status icon and we still don't have any 228 // notifications to display, nothing needs to be done. 229 return; 230 } 231 232 // See the comments in ash/system/web_notification/web_notification_tray.cc 233 // for why PostTask. 234 should_update_tray_content_ = true; 235 base::MessageLoop::current()->PostTask( 236 FROM_HERE, 237 base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr())); 238 } 239 240 void WebNotificationTray::OnStatusIconClicked() { 241 // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura. 242 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 243 mouse_click_point_ = screen->GetCursorScreenPoint(); 244 message_center_tray_->ToggleMessageCenterBubble(); 245 } 246 247 void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) { 248 if (command_id == kToggleQuietMode) { 249 bool in_quiet_mode = message_center()->IsQuietMode(); 250 message_center()->SetQuietMode(!in_quiet_mode); 251 return; 252 } 253 base::TimeDelta expires_in = command_id == kEnableQuietModeDay 254 ? base::TimeDelta::FromDays(1) 255 : base::TimeDelta::FromHours(1); 256 message_center()->EnterQuietModeWithExpire(expires_in); 257 } 258 259 void WebNotificationTray::UpdateStatusIcon() { 260 if (!should_update_tray_content_) 261 return; 262 should_update_tray_content_ = false; 263 264 int unread_notifications = message_center()->UnreadNotificationCount(); 265 266 base::string16 tool_tip; 267 if (unread_notifications > 0) { 268 base::string16 str_unread_count = base::FormatNumber(unread_notifications); 269 tool_tip = l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP_UNREAD, 270 str_unread_count); 271 } else { 272 tool_tip = l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_TOOLTIP); 273 } 274 275 gfx::ImageSkia* icon_image = GetIcon( 276 unread_notifications, 277 message_center()->IsQuietMode()); 278 279 if (status_icon_) { 280 status_icon_->SetImage(*icon_image); 281 status_icon_->SetToolTip(tool_tip); 282 return; 283 } 284 285 CreateStatusIcon(*icon_image, tool_tip); 286 } 287 288 void WebNotificationTray::SendHideMessageCenter() { 289 message_center_tray_->HideMessageCenterBubble(); 290 } 291 292 void WebNotificationTray::MarkMessageCenterHidden() { 293 if (message_center_delegate_) { 294 message_center_tray_->MarkMessageCenterHidden(); 295 message_center_delegate_ = NULL; 296 } 297 } 298 299 PositionInfo WebNotificationTray::GetPositionInfo() { 300 PositionInfo pos_info; 301 302 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 303 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 304 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding); 305 306 gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_); 307 308 pos_info.taskbar_alignment = internal::GetTaskbarAlignment(); 309 310 // We assume the taskbar is either at the top or at the bottom if we are not 311 // able to find it. 312 if (pos_info.taskbar_alignment == ALIGNMENT_NONE) { 313 if (mouse_click_point_.y() > corner.y()) 314 pos_info.taskbar_alignment = ALIGNMENT_TOP; 315 else 316 pos_info.taskbar_alignment = ALIGNMENT_BOTTOM; 317 } 318 319 pos_info.message_center_alignment = 320 internal::GetAnchorAlignment(work_area, corner); 321 322 pos_info.inital_anchor_point = corner; 323 pos_info.max_height = work_area.height(); 324 325 if (work_area.Contains(mouse_click_point_)) { 326 // Message center is in the work area. So position it few pixels above the 327 // mouse click point if alignemnt is towards bottom and few pixels below if 328 // alignment is towards top. 329 pos_info.inital_anchor_point.set_y( 330 mouse_click_point_.y() + 331 (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -kMouseOffset 332 : kMouseOffset)); 333 334 // Subtract the distance between mouse click point and the closest 335 // (insetted) edge from the max height to show the message center within the 336 // (insetted) work area bounds. Also subtract the offset from the mouse 337 // click point we added earlier. 338 pos_info.max_height -= 339 std::abs(mouse_click_point_.y() - corner.y()) + kMouseOffset; 340 } 341 return pos_info; 342 } 343 344 MessageCenterTray* WebNotificationTray::GetMessageCenterTray() { 345 return message_center_tray_.get(); 346 } 347 348 void WebNotificationTray::CreateStatusIcon(const gfx::ImageSkia& image, 349 const base::string16& tool_tip) { 350 if (status_icon_) 351 return; 352 353 StatusTray* status_tray = g_browser_process->status_tray(); 354 if (!status_tray) 355 return; 356 357 status_icon_ = status_tray->CreateStatusIcon( 358 StatusTray::NOTIFICATION_TRAY_ICON, image, tool_tip); 359 if (!status_icon_) 360 return; 361 362 status_icon_->AddObserver(this); 363 AddQuietModeMenu(status_icon_); 364 } 365 366 void WebNotificationTray::DestroyStatusIcon() { 367 if (!status_icon_) 368 return; 369 370 status_icon_->RemoveObserver(this); 371 StatusTray* status_tray = g_browser_process->status_tray(); 372 if (status_tray) 373 status_tray->RemoveStatusIcon(status_icon_); 374 status_icon_menu_ = NULL; 375 status_icon_ = NULL; 376 } 377 378 void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) { 379 DCHECK(status_icon); 380 381 scoped_ptr<StatusIconMenuModel> menu(new StatusIconMenuModel(this)); 382 menu->AddCheckItem(kToggleQuietMode, 383 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE)); 384 menu->SetCommandIdChecked(kToggleQuietMode, message_center()->IsQuietMode()); 385 menu->AddItem(kEnableQuietModeHour, 386 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1HOUR)); 387 menu->AddItem(kEnableQuietModeDay, 388 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_QUIET_MODE_1DAY)); 389 390 status_icon_menu_ = menu.get(); 391 status_icon->SetContextMenu(menu.Pass()); 392 } 393 394 MessageCenterWidgetDelegate* 395 WebNotificationTray::GetMessageCenterWidgetDelegateForTest() { 396 return message_center_delegate_; 397 } 398 399 } // namespace message_center 400