web_notification_tray_win.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 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_win.h" 6 7#include "base/i18n/number_formatting.h" 8#include "base/string16.h" 9#include "base/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 "chrome/browser/ui/views/message_center/notification_bubble_wrapper_win.h" 14#include "chrome/browser/ui/views/status_icons/status_icon_win.h" 15#include "content/public/browser/user_metrics.h" 16#include "grit/chromium_strings.h" 17#include "grit/theme_resources.h" 18#include "grit/ui_strings.h" 19#include "ui/base/l10n/l10n_util.h" 20#include "ui/base/models/simple_menu_model.h" 21#include "ui/base/resource/resource_bundle.h" 22#include "ui/base/win/hwnd_util.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_bubble_base.h" 31#include "ui/message_center/views/message_center_bubble.h" 32#include "ui/message_center/views/message_popup_collection.h" 33#include "ui/views/widget/widget.h" 34 35namespace { 36 37// Tray constants 38const int kScreenEdgePadding = 2; 39 40const int kSystemTrayWidth = 16; 41const int kSystemTrayHeight = 16; 42const int kNumberOfSystemTraySprites = 10; 43 44gfx::Rect GetCornerAnchorRect() { 45 // TODO(dewittj): Use the preference to determine which corner to anchor from. 46 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 47 gfx::Rect rect = screen->GetPrimaryDisplay().work_area(); 48 rect.Inset(kScreenEdgePadding, kScreenEdgePadding); 49 return gfx::Rect(rect.bottom_right(), gfx::Size()); 50} 51 52gfx::Point GetClosestCorner(gfx::Rect rect, gfx::Point query) { 53 gfx::Point center_point = rect.CenterPoint(); 54 gfx::Point rv; 55 56 if (query.x() > center_point.x()) 57 rv.set_x(rect.right()); 58 else 59 rv.set_x(rect.x()); 60 61 if (query.y() > center_point.y()) 62 rv.set_y(rect.bottom()); 63 else 64 rv.set_y(rect.y()); 65 66 return rv; 67} 68 69// GetMouseAnchorRect returns a rectangle that has one corner where the mouse 70// clicked, and the opposite corner at the closest corner of the work area 71// (inset by an appropriate margin.) 72gfx::Rect GetMouseAnchorRect(gfx::Point cursor) { 73 // TODO(dewittj): GetNativeScreen could be wrong for Aura. 74 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 75 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 76 work_area.Inset(kScreenEdgePadding, kScreenEdgePadding); 77 gfx::Point corner = GetClosestCorner(work_area, cursor); 78 79 gfx::Rect mouse_anchor_rect(gfx::BoundingRect(cursor, corner)); 80 return mouse_anchor_rect; 81} 82 83gfx::ImageSkia GetIcon(int unread_count) { 84 bool has_unread = unread_count > 0; 85 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 86 if (!has_unread) 87 return *rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_EMPTY); 88 89 // TODO(dewittj): Use scale factors other than 100P. 90 scoped_ptr<gfx::Canvas> canvas(new gfx::Canvas( 91 gfx::Size(kSystemTrayWidth, kSystemTrayHeight), 92 ui::SCALE_FACTOR_100P, 93 false)); 94 95 // Draw the attention-grabbing background image. 96 canvas->DrawImageInt( 97 *rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_ATTENTION), 0, 0); 98 99 // |numbers| is a sprite map with the image of a number from 1-9 and 9+. They 100 // are arranged horizontally, and have a transparent background. 101 gfx::ImageSkia* numbers = rb.GetImageSkiaNamed(IDR_NOTIFICATION_TRAY_NUMBERS); 102 103 // Assume that the last sprite is the catch-all for higher numbers of 104 // notifications. 105 int effective_unread = std::min(unread_count, kNumberOfSystemTraySprites); 106 int x_offset = (effective_unread - 1) * kSystemTrayWidth; 107 108 canvas->DrawImageInt(*numbers, 109 x_offset, 0, kSystemTrayWidth, kSystemTrayHeight, 110 0, 0, kSystemTrayWidth, kSystemTrayHeight, 111 false); 112 113 return gfx::ImageSkia(canvas->ExtractImageRep()); 114} 115 116} // namespace 117 118using content::UserMetricsAction; 119 120namespace message_center { 121 122MessageCenterTrayDelegate* CreateMessageCenterTray() { 123 return new WebNotificationTrayWin(); 124} 125 126WebNotificationTrayWin::WebNotificationTrayWin() 127 : status_icon_(NULL), 128 message_center_visible_(false), 129 should_update_tray_content_(true) { 130 message_center_tray_.reset(new MessageCenterTray( 131 this, g_browser_process->message_center())); 132 UpdateStatusIcon(); 133} 134 135WebNotificationTrayWin::~WebNotificationTrayWin() { 136 // Reset this early so that delegated events during destruction don't cause 137 // problems. 138 message_center_tray_.reset(); 139 DestroyStatusIcon(); 140} 141 142message_center::MessageCenter* WebNotificationTrayWin::message_center() { 143 return message_center_tray_->message_center(); 144} 145 146bool WebNotificationTrayWin::ShowPopups() { 147 popup_collection_.reset( 148 new message_center::MessagePopupCollection(NULL, message_center())); 149 return true; 150} 151 152void WebNotificationTrayWin::HidePopups() { 153 popup_collection_.reset(); 154} 155 156bool WebNotificationTrayWin::ShowMessageCenter() { 157 content::RecordAction(UserMetricsAction("Notifications.ShowMessageCenter")); 158 159 scoped_ptr<message_center::MessageCenterBubble> bubble( 160 new message_center::MessageCenterBubble(message_center())); 161 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 162 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 163 views::TrayBubbleView::AnchorAlignment alignment = GetAnchorAlignment(); 164 165 int max_height = work_area.height(); 166 167 // If the alignment is left- or right-oriented, the bubble can fill up the 168 // entire vertical height of the screen since the bubble is rendered to the 169 // side of the clicked icon. Otherwise we have to adjust for the arrow's 170 // height. 171 if (alignment == views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM || 172 alignment == views::TrayBubbleView::ANCHOR_ALIGNMENT_TOP) { 173 max_height -= 2*kScreenEdgePadding; 174 175 // If the work area contains the click point, then we know that the icon is 176 // not in the taskbar. Then we need to subtract the distance of the click 177 // point from the edge of the work area so we can see the whole bubble. 178 if (work_area.Contains(mouse_click_point_)) { 179 max_height -= std::min(mouse_click_point_.y() - work_area.y(), 180 work_area.bottom() - mouse_click_point_.y()); 181 } 182 } 183 bubble->SetMaxHeight(max_height); 184 185 message_center_bubble_.reset(new internal::NotificationBubbleWrapperWin( 186 this, 187 bubble.Pass(), 188 internal::NotificationBubbleWrapperWin::BUBBLE_TYPE_MESSAGE_CENTER)); 189 return true; 190} 191 192void WebNotificationTrayWin::HideMessageCenter() { 193 message_center_bubble_.reset(); 194} 195 196void WebNotificationTrayWin::UpdatePopups() { 197 // |popup_collection_| receives notification add/remove events and updates 198 // itself, so this method doesn't need to do anything. 199 // TODO(mukai): remove this method (currently this is used by 200 // non-rich-notifications in ChromeOS). 201}; 202 203void WebNotificationTrayWin::OnMessageCenterTrayChanged() { 204 // See the comments in ash/system/web_notification/web_notification_tray.cc 205 // for why PostTask. 206 should_update_tray_content_ = true; 207 MessageLoop::current()->PostTask( 208 FROM_HERE, 209 base::Bind(&WebNotificationTrayWin::UpdateStatusIcon, AsWeakPtr())); 210} 211 212gfx::Rect WebNotificationTrayWin::GetMessageCenterAnchor() { 213 return GetMouseAnchorRect(mouse_click_point_); 214} 215 216gfx::Rect WebNotificationTrayWin::GetPopupAnchor() { 217 return GetCornerAnchorRect(); 218} 219 220views::TrayBubbleView::AnchorAlignment 221WebNotificationTrayWin::GetAnchorAlignment() { 222 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 223 // TODO(dewittj): It's possible GetPrimaryDisplay is wrong. 224 gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds(); 225 gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); 226 227 // Comparing the work area to the screen bounds gives us the location of the 228 // taskbar. If the work area is less tall than the screen, assume the taskbar 229 // is on the bottom, and cause the arrow to be displayed on the bottom of the 230 // bubble. Otherwise, cause the arrow to be displayed on the side of the 231 // bubble that the taskbar is on. 232 if (work_area.width() < screen_bounds.width()) { 233 if (work_area.x() > screen_bounds.x()) 234 return views::TrayBubbleView::ANCHOR_ALIGNMENT_LEFT; 235 return views::TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT; 236 } 237 return views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM; 238} 239 240gfx::NativeView WebNotificationTrayWin::GetBubbleWindowContainer() { 241 return NULL; 242} 243 244void WebNotificationTrayWin::UpdateStatusIcon() { 245 if (!should_update_tray_content_) 246 return; 247 should_update_tray_content_ = false; 248 249 int total_notifications = message_center()->NotificationCount(); 250 if (total_notifications == 0) { 251 DestroyStatusIcon(); 252 return; 253 } 254 255 int unread_notifications = message_center()->UnreadNotificationCount(); 256 StatusIcon* status_icon = GetStatusIcon(); 257 if (!status_icon) 258 return; 259 260 status_icon->SetImage(GetIcon(unread_notifications)); 261 262 string16 product_name(l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME)); 263 if (unread_notifications > 0) { 264 string16 str_unread_count = base::FormatNumber(unread_notifications); 265 status_icon->SetToolTip(l10n_util::GetStringFUTF16( 266 IDS_MESSAGE_CENTER_TOOLTIP_UNREAD, product_name, str_unread_count)); 267 } else { 268 status_icon->SetToolTip(l10n_util::GetStringFUTF16( 269 IDS_MESSAGE_CENTER_TOOLTIP, product_name)); 270 } 271} 272 273void WebNotificationTrayWin::OnStatusIconClicked() { 274 // TODO(dewittj): It's possible GetNativeScreen is wrong for win-aura. 275 gfx::Screen* screen = gfx::Screen::GetNativeScreen(); 276 mouse_click_point_ = screen->GetCursorScreenPoint(); 277 message_center_tray_->ToggleMessageCenterBubble(); 278} 279 280void WebNotificationTrayWin::HideBubbleWithView( 281 const views::TrayBubbleView* bubble_view) { 282 if (message_center_bubble_.get() && 283 bubble_view == message_center_bubble_->bubble_view()) { 284 message_center_tray_->HideMessageCenterBubble(); 285 } 286} 287 288StatusIcon* WebNotificationTrayWin::GetStatusIcon() { 289 if (status_icon_) 290 return status_icon_; 291 292 StatusTray* status_tray = g_browser_process->status_tray(); 293 if (!status_tray) 294 return NULL; 295 296 StatusIcon* status_icon = status_tray->CreateStatusIcon(); 297 if (!status_icon) 298 return NULL; 299 300 status_icon_ = status_icon; 301 status_icon_->AddObserver(this); 302 AddQuietModeMenu(status_icon_); 303 304 return status_icon_; 305} 306 307void WebNotificationTrayWin::DestroyStatusIcon() { 308 if (!status_icon_) 309 return; 310 311 status_icon_->RemoveObserver(this); 312 StatusTray* status_tray = g_browser_process->status_tray(); 313 if (status_tray) 314 status_tray->RemoveStatusIcon(status_icon_); 315 status_icon_ = NULL; 316} 317 318void WebNotificationTrayWin::AddQuietModeMenu(StatusIcon* status_icon) { 319 DCHECK(status_icon); 320 status_icon->SetContextMenu(message_center_tray_->CreateQuietModeMenu()); 321} 322 323message_center::MessageCenterBubble* 324WebNotificationTrayWin::GetMessageCenterBubbleForTest() { 325 if (!message_center_bubble_.get()) 326 return NULL; 327 return static_cast<message_center::MessageCenterBubble*>( 328 message_center_bubble_->bubble()); 329} 330 331} // namespace message_center 332