web_notification_tray.cc revision 2385ea399aae016c0806a4f9ef3c9cfe3d2a39df
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_tray.h" 13#include "content/public/browser/notification_service.h" 14#include "content/public/browser/user_metrics.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 42gfx::ImageSkia* GetIcon(int unread_count, bool is_quiet_mode) { 43 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 44 int resource_id = IDR_NOTIFICATION_TRAY_EMPTY; 45 46 if (unread_count) { 47 if (is_quiet_mode) 48 resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_ATTENTION; 49 else 50 resource_id = IDR_NOTIFICATION_TRAY_ATTENTION; 51 } else if (is_quiet_mode) { 52 resource_id = IDR_NOTIFICATION_TRAY_DO_NOT_DISTURB_EMPTY; 53 } 54 55 return rb.GetImageSkiaNamed(resource_id); 56} 57 58} // namespace 59 60using content::UserMetricsAction; 61 62namespace message_center { 63 64namespace internal { 65 66// Gets the position of the taskbar from the work area bounds. Returns 67// ALIGNMENT_NONE if position cannot be found. 68Alignment GetTaskbarAlignment() { 69 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 70 // TODO(dewittj): It's possible GetPrimaryDisplay is wrong. 71 gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds(); 72 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 73 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding); 74 75 // Comparing the work area to the screen bounds gives us the location of the 76 // taskbar. If the work area is exactly the same as the screen bounds, 77 // we are unable to locate the taskbar so we say we don't know it's alignment. 78 if (work_area.height() < screen_bounds.height()) { 79 if (work_area.y() > screen_bounds.y()) 80 return ALIGNMENT_TOP; 81 return ALIGNMENT_BOTTOM; 82 } 83 if (work_area.width() < screen_bounds.width()) { 84 if (work_area.x() > screen_bounds.x()) 85 return ALIGNMENT_LEFT; 86 return ALIGNMENT_RIGHT; 87 } 88 89 return ALIGNMENT_NONE; 90} 91 92gfx::Point GetClosestCorner(const gfx::Rect& rect, const gfx::Point& query) { 93 gfx::Point center_point = rect.CenterPoint(); 94 gfx::Point rv; 95 96 if (query.x() > center_point.x()) 97 rv.set_x(rect.right()); 98 else 99 rv.set_x(rect.x()); 100 101 if (query.y() > center_point.y()) 102 rv.set_y(rect.bottom()); 103 else 104 rv.set_y(rect.y()); 105 106 return rv; 107} 108 109// Gets the corner of the screen where the message center should pop up. 110Alignment GetAnchorAlignment(const gfx::Rect& work_area, gfx::Point corner) { 111 gfx::Point center = work_area.CenterPoint(); 112 113 Alignment anchor_alignment = 114 center.y() > corner.y() ? ALIGNMENT_TOP : ALIGNMENT_BOTTOM; 115 anchor_alignment = 116 (Alignment)(anchor_alignment | 117 (center.x() > corner.x() ? ALIGNMENT_LEFT : ALIGNMENT_RIGHT)); 118 119 return anchor_alignment; 120} 121 122} // namespace internal 123 124MessageCenterTrayDelegate* CreateMessageCenterTray() { 125 return new WebNotificationTray(); 126} 127 128WebNotificationTray::WebNotificationTray() 129 : message_center_delegate_(NULL), 130 status_icon_(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 OnMessageCenterTrayChanged(); 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() { popup_collection_.reset(); } 156 157bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) { 158 content::RecordAction(UserMetricsAction("Notifications.ShowMessageCenter")); 159 160 // Message center delegate will be set to NULL when the message center 161 // widget's Close method is called so we don't need to worry about 162 // use-after-free issues. 163 message_center_delegate_ = new MessageCenterWidgetDelegate( 164 this, 165 message_center_tray_.get(), 166 show_settings, // settings initally invisible 167 GetPositionInfo()); 168 169 return true; 170} 171 172bool WebNotificationTray::ShowMessageCenter() { 173 return ShowMessageCenterInternal(/*show_settings =*/false); 174} 175 176void WebNotificationTray::HideMessageCenter() { 177 if (message_center_delegate_) { 178 views::Widget* widget = message_center_delegate_->GetWidget(); 179 if (widget) 180 widget->Close(); 181 } 182} 183 184bool WebNotificationTray::ShowNotifierSettings() { 185 if (message_center_delegate_) { 186 message_center_delegate_->SetSettingsVisible(true); 187 return true; 188 } 189 return ShowMessageCenterInternal(/*show_settings =*/true); 190} 191 192void WebNotificationTray::OnMessageCenterTrayChanged() { 193 // See the comments in ash/system/web_notification/web_notification_tray.cc 194 // for why PostTask. 195 should_update_tray_content_ = true; 196 base::MessageLoop::current()->PostTask( 197 FROM_HERE, 198 base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr())); 199} 200 201void WebNotificationTray::OnStatusIconClicked() { 202 // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura. 203 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 204 mouse_click_point_ = screen->GetCursorScreenPoint(); 205 message_center_tray_->ToggleMessageCenterBubble(); 206} 207 208void WebNotificationTray::UpdateStatusIcon() { 209 if (!should_update_tray_content_) 210 return; 211 should_update_tray_content_ = false; 212 213 int unread_notifications = message_center()->UnreadNotificationCount(); 214 215 string16 tool_tip; 216 if (unread_notifications > 0) { 217 string16 str_unread_count = base::FormatNumber(unread_notifications); 218 tool_tip = l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP_UNREAD, 219 str_unread_count); 220 } else { 221 tool_tip = l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_TOOLTIP); 222 } 223 224 gfx::ImageSkia* icon_image = GetIcon( 225 unread_notifications, 226 message_center()->IsQuietMode()); 227 228 if (status_icon_) { 229 status_icon_->SetImage(*icon_image); 230 status_icon_->SetToolTip(tool_tip); 231 return; 232 } 233 234 CreateStatusIcon(*icon_image, tool_tip); 235} 236 237void WebNotificationTray::SendHideMessageCenter() { 238 message_center_tray_->HideMessageCenterBubble(); 239} 240 241void WebNotificationTray::MarkMessageCenterHidden() { 242 if (message_center_delegate_) { 243 message_center_tray_->MarkMessageCenterHidden(); 244 message_center_delegate_ = NULL; 245 } 246} 247 248PositionInfo WebNotificationTray::GetPositionInfo() { 249 PositionInfo pos_info; 250 251 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 252 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 253 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding); 254 255 gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_); 256 257 pos_info.taskbar_alignment = internal::GetTaskbarAlignment(); 258 259 // We assume the taskbar is either at the top or at the bottom if we are not 260 // able to find it. 261 if (pos_info.taskbar_alignment == ALIGNMENT_NONE) { 262 if (mouse_click_point_.y() > corner.y()) 263 pos_info.taskbar_alignment = ALIGNMENT_TOP; 264 else 265 pos_info.taskbar_alignment = ALIGNMENT_BOTTOM; 266 } 267 268 pos_info.message_center_alignment = 269 internal::GetAnchorAlignment(work_area, corner); 270 271 pos_info.inital_anchor_point = corner; 272 pos_info.max_height = work_area.height(); 273 274 if (work_area.Contains(mouse_click_point_)) { 275 // Message center is in the work area. So position it few pixels above the 276 // mouse click point if alignemnt is towards bottom and few pixels below if 277 // alignment is towards top. 278 pos_info.inital_anchor_point.set_y( 279 mouse_click_point_.y() + 280 (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -kMouseOffset 281 : kMouseOffset)); 282 283 // Subtract the distance between mouse click point and the closest 284 // (insetted) edge from the max height to show the message center within the 285 // (insetted) work area bounds. Also subtract the offset from the mouse 286 // click point we added earlier. 287 pos_info.max_height -= 288 std::abs(mouse_click_point_.y() - corner.y()) + kMouseOffset; 289 } 290 return pos_info; 291} 292 293MessageCenterTray* WebNotificationTray::GetMessageCenterTray() { 294 return message_center_tray_.get(); 295} 296 297void WebNotificationTray::CreateStatusIcon(const gfx::ImageSkia& image, 298 const string16& tool_tip) { 299 if (status_icon_) 300 return; 301 302 StatusTray* status_tray = g_browser_process->status_tray(); 303 if (!status_tray) 304 return; 305 306 status_icon_ = status_tray->CreateStatusIcon( 307 StatusTray::NOTIFICATION_TRAY_ICON, image, tool_tip); 308 if (!status_icon_) 309 return; 310 311 status_icon_->AddObserver(this); 312 AddQuietModeMenu(status_icon_); 313} 314 315void WebNotificationTray::DestroyStatusIcon() { 316 if (!status_icon_) 317 return; 318 319 status_icon_->RemoveObserver(this); 320 StatusTray* status_tray = g_browser_process->status_tray(); 321 if (status_tray) 322 status_tray->RemoveStatusIcon(status_icon_); 323 status_icon_ = NULL; 324} 325 326void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) { 327 DCHECK(status_icon); 328 status_icon->SetContextMenu(message_center_tray_->CreateQuietModeMenu()); 329} 330 331MessageCenterWidgetDelegate* 332WebNotificationTray::GetMessageCenterWidgetDelegateForTest() { 333 return message_center_delegate_; 334} 335 336} // namespace message_center 337