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