Chromium Code Reviews| Index: chrome/browser/ui/views/message_center/message_center_tray_host_win.cc |
| diff --git a/chrome/browser/ui/views/message_center/message_center_tray_host_win.cc b/chrome/browser/ui/views/message_center/message_center_tray_host_win.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f1e0674ef2a7b52c66578704cb6322113baa4a4b |
| --- /dev/null |
| +++ b/chrome/browser/ui/views/message_center/message_center_tray_host_win.cc |
| @@ -0,0 +1,351 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/ui/views/message_center/message_center_tray_host_win.h" |
| + |
| +#include "base/memory/singleton.h" |
| +#include "base/utf_string_conversions.h" |
| +#include "chrome/browser/browser_process.h" |
| +#include "chrome/browser/status_icons/status_icon.h" |
| +#include "chrome/browser/status_icons/status_tray.h" |
| +#include "chrome/browser/ui/message_center/message_center_util.h" |
| +#include "grit/theme_resources.h" |
| +#include "ui/base/resource/resource_bundle.h" |
| +#include "ui/base/win/hwnd_util.h" |
| +#include "ui/gfx/image/image_skia_operations.h" |
| +#include "ui/gfx/screen.h" |
| +#include "ui/message_center/message_bubble_base.h" |
| +#include "ui/message_center/message_center_tray.h" |
| +#include "ui/views/widget/widget.h" |
| + |
| +namespace { |
| + |
| +// Tray constants |
| +const int kPaddingFromLeftEdgeOfSystemTrayBottomAlignment = 8; |
| + |
| +gfx::Rect GetCornerAnchorRect(gfx::Size preferred_size) { |
| + gfx::Screen* screen = gfx::Screen::GetNativeScreen(); |
| + gfx::Point cursor = screen->GetCursorScreenPoint(); |
| + gfx::Rect rect = screen->GetPrimaryDisplay().work_area(); |
| + rect.Inset(10, 5); |
| + gfx::Point bottom_right( |
| + rect.bottom_right().x() - preferred_size.width() / 2, |
| + rect.bottom_right().y()); |
| + return gfx::Rect(bottom_right, gfx::Size()); |
| +} |
| + |
| +// GetMouseAnchorRect returns a rectangle that is near the cursor point, but |
| +// whose behavior depends on where the Windows taskbar is. If it is on the |
| +// top or bottom of the screen, we want the arrow to touch the edge of the |
| +// taskbar directly above or below the mouse pointer and within the work area. |
| +// Otherwise, position the anchor on the mouse cursor directly. |
| +gfx::Rect GetMouseAnchorRect() { |
| + gfx::Screen* screen = gfx::Screen::GetNativeScreen(); |
| + gfx::Point cursor = screen->GetCursorScreenPoint(); |
| + gfx::Rect rect = screen->GetPrimaryDisplay().bounds(); |
| + gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); |
| + |
| + // Inset the rectangle by the taskbar width if it is on top or bottom. |
| + rect.set_y(work_area.y()); |
| + rect.set_height(work_area.height()); |
| + |
| + rect.Inset(kPaddingFromLeftEdgeOfSystemTrayBottomAlignment, 0); |
| + |
| + // Want to find a mouse point that is on the mouse cursor, unless the mouse is |
| + // over the start menu and the start menu is on the top or bottom. |
| + gfx::Rect mouse_anchor_rect(gfx::BoundingRect(cursor, rect.bottom_right())); |
| + mouse_anchor_rect.set_height(0); |
| + if (!rect.Contains(cursor)) |
| + mouse_anchor_rect.AdjustToFit(rect); |
| + mouse_anchor_rect.set_width(0); |
| + return mouse_anchor_rect; |
| +} |
| + |
| +} // namespace |
| + |
| +#if defined(ENABLE_MESSAGE_CENTER) |
| + |
| +namespace chrome { |
| + |
| +// static |
| +ui::MessageCenterTrayDelegate* GetMessageCenterTray() { |
| + return ui::MessageCenterTrayHostWin::GetInstance(); |
| +} |
| + |
| +} // namespace chrome |
| + |
| +#endif |
| + |
| + |
| +namespace ui { |
| + |
| +namespace internal { |
| + |
| +class WebNotificationBubbleWrapper |
| + : public views::WidgetObserver, |
| + public views::TrayBubbleView::Delegate { |
| + public: |
| + // Takes ownership of |bubble| and creates |bubble_wrapper_|. |
| + WebNotificationBubbleWrapper(MessageCenterTrayHostWin* tray, |
|
Pete Williamson
2013/01/17 19:07:45
Make this a platform specific class in its own fil
dewittj
2013/01/18 00:57:46
Done.
|
| + message_center::MessageBubbleBase* bubble, |
| + AnchorType anchor_type) |
| + : tray_(tray) { |
| + bubble_.reset(bubble); |
| + |
| + // Windows-specific initialization. |
| + views::TrayBubbleView::AnchorAlignment anchor_alignment = |
| + tray->GetAnchorAlignment(); |
| + views::TrayBubbleView::InitParams init_params = |
| + bubble->GetInitParams(anchor_alignment); |
| + init_params.anchor_type = anchor_type; |
| + init_params.close_on_deactivate = false; |
| + init_params.arrow_alignment = |
| + views::BubbleBorder::ALIGN_ARROW_TO_MID_ANCHOR; |
| + // TODO(dewittj): Show big shadow without blocking clicks. |
| + init_params.shadow = views::BubbleBorder::NO_SHADOW; |
| + |
| + bubble_view_ = views::TrayBubbleView::Create( |
| + tray->GetBubbleWindowContainer(), NULL, this, &init_params); |
| + |
| + bubble_widget_ = views::BubbleDelegateView::CreateBubble(bubble_view_); |
| + bubble_widget_->AddObserver(this); |
| + bubble_widget_->StackAtTop(); |
| + bubble_widget_->SetAlwaysOnTop(true); |
| + bubble_widget_->Activate(); |
| + bubble_view_->InitializeAndShowBubble(); |
| + |
| + bubble_view_->set_close_on_deactivate(true); |
| + bubble->InitializeContents(bubble_view_); |
| + } |
| + ~WebNotificationBubbleWrapper() { |
| + bubble_.reset(); |
| + if (bubble_widget_) { |
| + bubble_widget_->RemoveObserver(this); |
| + bubble_widget_->Close(); |
| + bubble_widget_ = NULL; |
| + } |
| + } |
| + |
| + // Overridden from views::WidgetObserver. |
| + void OnWidgetClosing(views::Widget* widget) { |
| + bubble_widget_->RemoveObserver(this); |
| + bubble_widget_ = NULL; |
| + tray_->HideBubbleWithView(bubble_view_); |
| + } |
| + |
| + // TrayBubbleView::Delegate implementation. |
| + // Called when the view is destroyed. Any pointers to the view should be |
| + // cleared when this gets called. |
| + virtual void BubbleViewDestroyed() { |
| + bubble_->BubbleViewDestroyed(); |
| + } |
| + |
| + // Called when the mouse enters/exits the view. |
| + virtual void OnMouseEnteredView() { |
| + bubble_->OnMouseEnteredView(); |
| + }; |
| + virtual void OnMouseExitedView() { |
| + bubble_->OnMouseExitedView(); |
| + } |
| + |
| + // Called from GetAccessibleState(); should return the appropriate |
| + // accessible name for the bubble. |
| + virtual string16 GetAccessibleNameForBubble() { |
| + // TODO(dewittj): get a string resource. |
| + return ASCIIToUTF16("Windows Notification Center"); |
| + } |
| + |
| + // Passes responsibility for BubbleDelegateView::GetAnchorRect to the |
| + // delegate. |
| + virtual gfx::Rect GetAnchorRect(views::Widget* anchor_widget, |
| + AnchorType anchor_type, |
| + AnchorAlignment anchor_alignment) { |
| + gfx::Size size = bubble_view_->GetPreferredSize(); |
| + return tray_->GetAnchorRect(size, |
| + anchor_type, |
| + anchor_alignment); |
| + } |
| + |
| + // Called when a bubble wants to hide/destroy itself (e.g. last visible |
| + // child view was closed). |
| + virtual void HideBubble(const views::TrayBubbleView* bubble_view) { |
| + tray_->HideBubbleWithView(bubble_view); |
| + } |
| + |
| + // Convenience accessors. |
| + views::TrayBubbleView* bubble_view() const { return bubble_view_; } |
| + views::Widget* bubble_widget() const { |
| + return bubble_widget_; |
| + } |
| + message_center::MessageBubbleBase* bubble() const { return bubble_.get(); } |
| + |
| + private: |
| + scoped_ptr<message_center::MessageBubbleBase> bubble_; |
| + // Unowned. |
| + views::TrayBubbleView* bubble_view_; |
| + views::Widget* bubble_widget_; |
| + MessageCenterTrayHostWin* tray_; |
| +}; |
| + |
| +} // namespace internal |
| + |
| +// TODO(dewittj): Un-singleton. |
| +MessageCenterTrayHostWin* MessageCenterTrayHostWin::GetInstance() { |
| + return Singleton<MessageCenterTrayHostWin>::get(); |
| +} |
| + |
| +MessageCenterTrayHostWin::MessageCenterTrayHostWin() |
| + : status_icon_(NULL), |
| + message_center_visible_(false) { |
| + message_center_tray_ = new MessageCenterTray(this); |
| + message_center_tray_->AddObserver(this); |
| +} |
| + |
| +MessageCenterTrayHostWin::~MessageCenterTrayHostWin() { |
| + message_center_tray_->RemoveObserver(this); |
| + if (status_icon_ != NULL) { |
| + status_icon_->RemoveObserver(this); |
| + StatusTray * status_tray = g_browser_process->status_tray(); |
| + status_tray->RemoveStatusIcon(status_icon_); |
| + status_icon_ = NULL; |
| + } |
| +} |
| + |
| +message_center::MessageCenter* MessageCenterTrayHostWin::message_center() { |
| + return message_center_tray_->message_center(); |
| +} |
| + |
| +bool MessageCenterTrayHostWin::ShowPopups( |
| + message_center::MessageBubbleBase* bubble) { |
| + if (!CanShowPopups()) |
| + return false; |
| + popup_bubble_.reset(new internal::WebNotificationBubbleWrapper( |
| + this, |
| + bubble, |
| + views::TrayBubbleView::ANCHOR_TYPE_BUBBLE)); |
| + return true; |
| +} |
| +void MessageCenterTrayHostWin::HidePopups() { |
| + popup_bubble_.reset(); |
| +} |
| +bool MessageCenterTrayHostWin::ShowMessageCenter( |
| + message_center::MessageBubbleBase* bubble) { |
| + // TODO(dewittj): CanShowMessageCenter. |
| + |
| + gfx::Screen* screen = gfx::Screen::GetNativeScreen(); |
| + gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); |
| + gfx::Point work_area_center = work_area.CenterPoint(); |
| + gfx::Point anchor_center = message_center_anchor_rect_.CenterPoint(); |
| + int max_height = 0; |
| + if (work_area_center < anchor_center) |
| + max_height = anchor_center.y() - work_area.origin().y(); |
| + else |
| + max_height = work_area.bottom() - message_center_anchor_rect_.bottom(); |
| + bubble->SetMaxHeight(max_height); |
| + |
| + message_center_bubble_.reset(new internal::WebNotificationBubbleWrapper( |
| + this, |
| + bubble, |
| + views::TrayBubbleView::ANCHOR_TYPE_TRAY)); |
| + // TODO(dewittj): Prevent auto-hide? |
| + return true; |
| +} |
| + |
| +void MessageCenterTrayHostWin::HideMessageCenter() { |
| + message_center_bubble_.reset(); |
| +} |
| + |
| +void MessageCenterTrayHostWin::UpdateMessageCenter() { |
| + if (message_center_bubble_.get()) |
| + message_center_bubble_->bubble()->ScheduleUpdate(); |
| +} |
| + |
| +void MessageCenterTrayHostWin::UpdatePopups() { |
| + if (popup_bubble_.get()) |
| + popup_bubble_->bubble()->ScheduleUpdate(); |
| +}; |
| + |
| + |
| + |
| +void MessageCenterTrayHostWin::OnMessageCenterTrayChanged() { |
| + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| + bool has_notifications = message_center()->NotificationCount() > 0; |
| + StatusTray* status_tray = g_browser_process->status_tray(); |
| + if (has_notifications) { |
| + if (status_icon_ == NULL) { |
| + status_icon_ = status_tray->CreateStatusIcon(); |
| + status_icon_->AddObserver(this); |
| + } |
| + // TODO(dewittj): Get some icons. |
| + gfx::ImageSkia* icon = |
| + rb.GetImageSkiaNamed(IDR_ALLOWED_NOTIFICATION); |
| + if (message_center()->UnreadNotificationCount() > 0) { |
| + status_icon_->SetImage(*icon); |
| + } else { |
| + status_icon_->SetImage( |
| + gfx::ImageSkiaOperations::CreateTransparentImage(*icon, .5)); |
| + } |
| + } else if (status_icon_ != NULL) { |
| + status_tray->RemoveStatusIcon(status_icon_); |
| + status_icon_ = NULL; |
| + } |
| +} |
| + |
| +gfx::Rect MessageCenterTrayHostWin::GetAnchorRect( |
| + gfx::Size preferred_size, |
| + views::TrayBubbleView::AnchorType anchor_type, |
| + views::TrayBubbleView::AnchorAlignment anchor_alignment) { |
| + // |message_center_visible_| is set before the bubble is actually rendered, |
| + // so the flag can be used to determine which anchor to use. |
| + if (anchor_type == views::TrayBubbleView::ANCHOR_TYPE_TRAY) { |
| + return message_center_anchor_rect_; |
| + } |
| + return GetCornerAnchorRect(preferred_size); |
| +} |
| + |
| +bool MessageCenterTrayHostWin::CanShowPopups() { |
|
Pete Williamson
2013/01/17 19:07:45
Check to see if we still need this method.
|
| + // TODO(dewittj): This will eventually depend on whether quiet mode is active. |
| + return true; |
| +} |
| + |
| +views::TrayBubbleView::AnchorAlignment |
| +MessageCenterTrayHostWin::GetAnchorAlignment() { |
| + gfx::Screen* screen = gfx::Screen::GetNativeScreen(); |
| + // TODO(dewittj): It's possible GetPrimaryDisplay is wrong. |
| + gfx::Rect screen_bounds = screen->GetPrimaryDisplay().bounds(); |
| + gfx::Rect work_area = screen->GetPrimaryDisplay().work_area(); |
| + |
| + if (work_area.height() < screen_bounds.height()) |
| + return views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM; |
| + if (work_area.x() > screen_bounds.x()) |
| + return views::TrayBubbleView::ANCHOR_ALIGNMENT_LEFT; |
| + return views::TrayBubbleView::ANCHOR_ALIGNMENT_RIGHT; |
| +} |
| + |
| +gfx::NativeView MessageCenterTrayHostWin::GetBubbleWindowContainer() { |
| + return NULL; |
| +} |
| + |
| + |
| +void MessageCenterTrayHostWin::OnStatusIconClicked() { |
| + UpdateAnchorRect(); |
| + message_center_tray_->ToggleMessageCenterBubble(); |
| +} |
| + |
| +void MessageCenterTrayHostWin::HideBubbleWithView( |
| + const views::TrayBubbleView* bubble_view) { |
| + if (message_center_bubble_.get() && |
| + bubble_view == message_center_bubble_->bubble_view()) { |
| + message_center_tray_->HideMessageCenterBubble(); |
| + } else if (popup_bubble_.get() && |
| + bubble_view == popup_bubble_->bubble_view()) { |
| + message_center_tray_->HidePopupBubble(); |
| + } |
| +} |
| + |
| +void MessageCenterTrayHostWin::UpdateAnchorRect() { |
| + message_center_anchor_rect_ = GetMouseAnchorRect(); |
| +} |
| + |
| +} // namespace ui |