web_notification_tray.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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/user_metrics.h" 14#include "grit/chromium_strings.h" 15#include "grit/theme_resources.h" 16#include "grit/ui_strings.h" 17#include "ui/base/l10n/l10n_util.h" 18#include "ui/base/resource/resource_bundle.h" 19#include "ui/gfx/canvas.h" 20#include "ui/gfx/image/image_skia_operations.h" 21#include "ui/gfx/rect.h" 22#include "ui/gfx/screen.h" 23#include "ui/gfx/size.h" 24#include "ui/message_center/message_center_tray.h" 25#include "ui/message_center/message_center_tray_delegate.h" 26#include "ui/message_center/views/message_popup_collection.h" 27#include "ui/views/widget/widget.h" 28 29namespace { 30 31// Tray constants 32const int kScreenEdgePadding = 2; 33 34const int kSystemTrayWidth = 16; 35const int kSystemTrayHeight = 16; 36const int kNumberOfSystemTraySprites = 10; 37 38gfx::ImageSkia GetIcon(int unread_count) { 39 bool has_unread = unread_count > 0; 40 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 41 if (!has_unread) 42 return *rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_EMPTY); 43 44 return *rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_ATTENTION); 45} 46 47} // namespace 48 49using content::UserMetricsAction; 50 51namespace message_center { 52 53namespace internal { 54 55// Gets the position of the taskbar from the work area bounds. Returns 56// ALIGNMENT_NONE if position cannot be found. 57Alignment GetTaskbarAlignment() { 58 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 59 // TODO(dewittj): It's possible GetPrimaryDisplay is wrong. 60 gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds(); 61 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 62 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding); 63 64 // Comparing the work area to the screen bounds gives us the location of the 65 // taskbar. If the work area is exactly the same as the screen bounds, 66 // we are unable to locate the taskbar so we say we don't know it's alignment. 67 if (work_area.height() < screen_bounds.height()) { 68 if (work_area.y() > screen_bounds.y()) 69 return ALIGNMENT_TOP; 70 return ALIGNMENT_BOTTOM; 71 } 72 if (work_area.width() < screen_bounds.width()) { 73 if (work_area.x() > screen_bounds.x()) 74 return ALIGNMENT_LEFT; 75 return ALIGNMENT_RIGHT; 76 } 77 78 return ALIGNMENT_NONE; 79} 80 81gfx::Point GetClosestCorner(const gfx::Rect& rect, const gfx::Point& query) { 82 gfx::Point center_point = rect.CenterPoint(); 83 gfx::Point rv; 84 85 if (query.x() > center_point.x()) 86 rv.set_x(rect.right()); 87 else 88 rv.set_x(rect.x()); 89 90 if (query.y() > center_point.y()) 91 rv.set_y(rect.bottom()); 92 else 93 rv.set_y(rect.y()); 94 95 return rv; 96} 97 98// Gets the corner of the screen where the message center should pop up. 99Alignment GetAnchorAlignment(const gfx::Rect& work_area, gfx::Point corner) { 100 gfx::Point center = work_area.CenterPoint(); 101 102 Alignment anchor_alignment = 103 center.y() > corner.y() ? ALIGNMENT_TOP : ALIGNMENT_BOTTOM; 104 anchor_alignment = 105 (Alignment)(anchor_alignment | 106 (center.x() > corner.x() ? ALIGNMENT_LEFT : ALIGNMENT_RIGHT)); 107 108 return anchor_alignment; 109} 110 111} // namespace internal 112 113MessageCenterTrayDelegate* CreateMessageCenterTray() { 114 return new WebNotificationTray(); 115} 116 117WebNotificationTray::WebNotificationTray() 118 : message_center_delegate_(NULL), 119 status_icon_(NULL), 120 message_center_visible_(false), 121 should_update_tray_content_(true) { 122 message_center_tray_.reset( 123 new MessageCenterTray(this, g_browser_process->message_center())); 124 UpdateStatusIcon(); 125} 126 127WebNotificationTray::~WebNotificationTray() { 128 // Reset this early so that delegated events during destruction don't cause 129 // problems. 130 message_center_tray_.reset(); 131 DestroyStatusIcon(); 132} 133 134message_center::MessageCenter* WebNotificationTray::message_center() { 135 return message_center_tray_->message_center(); 136} 137 138bool WebNotificationTray::ShowPopups() { 139 popup_collection_.reset(new message_center::MessagePopupCollection( 140 NULL, message_center(), message_center_tray_.get())); 141 return true; 142} 143 144void WebNotificationTray::HidePopups() { popup_collection_.reset(); } 145 146bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) { 147 content::RecordAction(UserMetricsAction("Notifications.ShowMessageCenter")); 148 149 // Message center delegate will be set to NULL when the message center 150 // widget's Close method is called so we don't need to worry about 151 // use-after-free issues. 152 message_center_delegate_ = new MessageCenterWidgetDelegate( 153 this, 154 message_center_tray_.get(), 155 show_settings, // settings initally invisible 156 GetPositionInfo()); 157 158 return true; 159} 160 161bool WebNotificationTray::ShowMessageCenter() { 162 return ShowMessageCenterInternal(/*show_settings =*/false); 163} 164 165void WebNotificationTray::HideMessageCenter() { 166 if (message_center_delegate_) { 167 views::Widget* widget = message_center_delegate_->GetWidget(); 168 if (widget) 169 widget->Close(); 170 } 171} 172 173bool WebNotificationTray::ShowNotifierSettings() { 174 if (message_center_delegate_) { 175 message_center_delegate_->SetSettingsVisible(true); 176 return true; 177 } 178 return ShowMessageCenterInternal(/*show_settings =*/true); 179} 180 181void WebNotificationTray::OnMessageCenterTrayChanged() { 182 // See the comments in ash/system/web_notification/web_notification_tray.cc 183 // for why PostTask. 184 should_update_tray_content_ = true; 185 base::MessageLoop::current()->PostTask( 186 FROM_HERE, 187 base::Bind(&WebNotificationTray::UpdateStatusIcon, AsWeakPtr())); 188} 189 190void WebNotificationTray::OnStatusIconClicked() { 191 // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura. 192 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 193 mouse_click_point_ = screen->GetCursorScreenPoint(); 194 message_center_tray_->ToggleMessageCenterBubble(); 195} 196 197void WebNotificationTray::UpdateStatusIcon() { 198 if (!should_update_tray_content_) 199 return; 200 should_update_tray_content_ = false; 201 202 int total_notifications = message_center()->NotificationCount(); 203 if (total_notifications == 0) { 204 DestroyStatusIcon(); 205 return; 206 } 207 208 int unread_notifications = message_center()->UnreadNotificationCount(); 209 StatusIcon* status_icon = GetStatusIcon(); 210 if (!status_icon) 211 return; 212 213 status_icon->SetImage(GetIcon(unread_notifications)); 214 215 string16 product_name(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); 216 if (unread_notifications > 0) { 217 string16 str_unread_count = base::FormatNumber(unread_notifications); 218 status_icon->SetToolTip(l10n_util::GetStringFUTF16( 219 IDS_MESSAGE_CENTER_TOOLTIP_UNREAD, product_name, str_unread_count)); 220 } else { 221 status_icon->SetToolTip( 222 l10n_util::GetStringFUTF16(IDS_MESSAGE_CENTER_TOOLTIP, product_name)); 223 } 224} 225 226void WebNotificationTray::SendHideMessageCenter() { 227 message_center_tray_->HideMessageCenterBubble(); 228} 229 230void WebNotificationTray::MarkMessageCenterHidden() { 231 if (message_center_delegate_) { 232 message_center_tray_->MarkMessageCenterHidden(); 233 message_center_delegate_ = NULL; 234 } 235} 236 237PositionInfo WebNotificationTray::GetPositionInfo() { 238 PositionInfo pos_info; 239 240 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 241 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 242 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding); 243 244 gfx::Point corner = internal::GetClosestCorner(work_area, mouse_click_point_); 245 246 pos_info.taskbar_alignment = internal::GetTaskbarAlignment(); 247 248 // We assume the taskbar is either at the top or at the bottom if we are not 249 // able to find it. 250 if (pos_info.taskbar_alignment == ALIGNMENT_NONE) { 251 if (mouse_click_point_.y() > corner.y()) 252 pos_info.taskbar_alignment = ALIGNMENT_TOP; 253 else 254 pos_info.taskbar_alignment = ALIGNMENT_BOTTOM; 255 } 256 257 pos_info.message_center_alignment = 258 internal::GetAnchorAlignment(work_area, corner); 259 260 pos_info.inital_anchor_point = corner; 261 pos_info.max_height = work_area.height(); 262 263 if (work_area.Contains(mouse_click_point_)) { 264 pos_info.max_height -= std::abs(mouse_click_point_.y() - corner.y()); 265 266 // Message center is in the work area. So position it few pixels above the 267 // mouse click point if alignemnt is towards bottom and few pixels below if 268 // alignment is towards top. 269 pos_info.inital_anchor_point 270 .set_y(mouse_click_point_.y() + 271 (pos_info.message_center_alignment & ALIGNMENT_BOTTOM ? -5 : 5)); 272 } 273 return pos_info; 274} 275 276StatusIcon* WebNotificationTray::GetStatusIcon() { 277 if (status_icon_) 278 return status_icon_; 279 280 StatusTray* status_tray = g_browser_process->status_tray(); 281 if (!status_tray) 282 return NULL; 283 284 StatusIcon* status_icon = 285 status_tray->CreateStatusIcon(StatusTray::NOTIFICATION_TRAY_ICON); 286 if (!status_icon) 287 return NULL; 288 289 status_icon_ = status_icon; 290 status_icon_->AddObserver(this); 291 AddQuietModeMenu(status_icon_); 292 293 return status_icon_; 294} 295 296void WebNotificationTray::DestroyStatusIcon() { 297 if (!status_icon_) 298 return; 299 300 status_icon_->RemoveObserver(this); 301 StatusTray* status_tray = g_browser_process->status_tray(); 302 if (status_tray) 303 status_tray->RemoveStatusIcon(status_icon_); 304 status_icon_ = NULL; 305} 306 307void WebNotificationTray::AddQuietModeMenu(StatusIcon* status_icon) { 308 DCHECK(status_icon); 309 status_icon->SetContextMenu(message_center_tray_->CreateQuietModeMenu()); 310} 311 312MessageCenterWidgetDelegate* 313WebNotificationTray::GetMessageCenterWidgetDelegateForTest() { 314 return message_center_delegate_; 315} 316 317} // namespace message_center 318