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.
5 #include "ui/message_center/views/message_popup_collection.h"
10 #include "base/memory/weak_ptr.h"
11 #include "base/timer.h"
12 #include "ui/gfx/screen.h"
13 #include "ui/message_center/message_center.h"
14 #include "ui/message_center/message_center_constants.h"
15 #include "ui/message_center/notification.h"
16 #include "ui/message_center/notification_list.h"
17 #include "ui/message_center/views/notification_view.h"
18 #include "ui/views/background.h"
19 #include "ui/views/layout/fill_layout.h"
20 #include "ui/views/view.h"
21 #include "ui/views/widget/widget.h"
22 #include "ui/views/widget/widget_delegate.h"
24 namespace message_center
{
26 class ToastContentsView
: public views::WidgetDelegateView
{
28 ToastContentsView(const Notification
* notification
,
29 base::WeakPtr
<MessagePopupCollection
> collection
)
30 : collection_(collection
) {
33 set_notify_enter_exit_on_child(true);
34 // Sets the transparent background. Then, when the message view is slid out,
35 // the whole toast seems to slide although the actual bound of the widget
36 // remains. This is hacky but easier to keep the consistency.
37 set_background(views::Background::CreateSolidBackground(0, 0, 0, 0));
39 int seconds
= kAutocloseDefaultDelaySeconds
;
40 if (notification
->priority() > DEFAULT_PRIORITY
)
41 seconds
= kAutocloseHighPriorityDelaySeconds
;
42 delay_
= base::TimeDelta::FromSeconds(seconds
);
44 // Creates the timer only when it does the timeout (i.e. not never-timeout).
45 if (!notification
->never_timeout())
46 timer_
.reset(new base::OneShotTimer
<views::Widget
>);
49 views::Widget
* CreateWidget(gfx::NativeView parent
) {
50 views::Widget::InitParams
params(
51 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS
);
52 params
.keep_on_top
= true;
54 params
.parent
= parent
;
56 params
.top_level
= true;
57 params
.transparent
= true;
58 params
.delegate
= this;
59 views::Widget
* widget
= new views::Widget();
60 widget
->set_focus_on_creation(false);
65 void SetContents(MessageView
* view
) {
66 RemoveAllChildViews(true);
68 views::Widget
* widget
= GetWidget();
70 gfx::Rect bounds
= widget
->GetWindowBoundsInScreen();
71 bounds
.set_width(kNotificationWidth
);
72 bounds
.set_height(view
->GetHeightForWidth(kNotificationWidth
));
73 widget
->SetBounds(bounds
);
87 delay_
-= base::Time::Now() - start_time_
;
88 if (delay_
< base::TimeDelta())
98 start_time_
= base::Time::Now();
99 timer_
->Start(FROM_HERE
,
101 base::Bind(&views::Widget::Close
,
102 base::Unretained(GetWidget())));
105 // Overridden from views::WidgetDelegate:
106 virtual views::View
* GetContentsView() OVERRIDE
{
110 virtual void WindowClosing() OVERRIDE
{
111 if (timer_
.get() && timer_
->IsRunning())
115 virtual bool CanActivate() const OVERRIDE
{
116 #if defined(OS_WIN) && defined(USE_AURA)
123 // Overridden from views::View:
124 virtual void OnMouseEntered(const ui::MouseEvent
& event
) OVERRIDE
{
126 collection_
->OnMouseEntered();
129 virtual void OnMouseExited(const ui::MouseEvent
& event
) OVERRIDE
{
131 collection_
->OnMouseExited();
134 virtual void Layout() OVERRIDE
{
135 if (child_count() > 0)
136 child_at(0)->SetBounds(x(), y(), width(), height());
139 virtual gfx::Size
GetPreferredSize() OVERRIDE
{
140 if (child_count() == 0)
143 return gfx::Size(kNotificationWidth
,
144 child_at(0)->GetHeightForWidth(kNotificationWidth
));
148 base::TimeDelta delay_
;
149 base::Time start_time_
;
150 scoped_ptr
<base::OneShotTimer
<views::Widget
> > timer_
;
151 base::WeakPtr
<MessagePopupCollection
> collection_
;
153 DISALLOW_COPY_AND_ASSIGN(ToastContentsView
);
156 MessagePopupCollection::MessagePopupCollection(gfx::NativeView parent
,
157 MessageCenter
* message_center
)
159 message_center_(message_center
) {
160 DCHECK(message_center_
);
164 MessagePopupCollection::~MessagePopupCollection() {
168 void MessagePopupCollection::UpdatePopups() {
169 NotificationList::PopupNotifications popups
=
170 message_center_
->notification_list()->GetPopupNotifications();
172 if (popups
.empty()) {
179 // On Win+Aura, we don't have a parent since the popups currently show up
180 // on the Windows desktop, not in the Aura/Ash desktop. This code will
181 // display the popups on the primary display.
182 gfx::Screen
* screen
= gfx::Screen::GetNativeScreen();
183 work_area
= screen
->GetPrimaryDisplay().work_area();
185 gfx::Screen
* screen
= gfx::Screen::GetScreenFor(parent_
);
186 work_area
= screen
->GetDisplayNearestWindow(parent_
).work_area();
189 std::set
<std::string
> old_toast_ids
;
190 for (ToastContainer::iterator iter
= toasts_
.begin(); iter
!= toasts_
.end();
192 old_toast_ids
.insert(iter
->first
);
195 int bottom
= work_area
.bottom() - kMarginBetweenItems
;
196 int left
= work_area
.right() - kNotificationWidth
- kMarginBetweenItems
;
197 // Iterate in the reverse order to keep the oldest toasts on screen. Newer
198 // items may be ignored if there are no room to place them.
199 for (NotificationList::PopupNotifications::const_reverse_iterator iter
=
200 popups
.rbegin(); iter
!= popups
.rend(); ++iter
) {
202 NotificationView::Create(*(*iter
), message_center_
, true);
203 int view_height
= view
->GetHeightForWidth(kNotificationWidth
);
204 if (bottom
- view_height
- kMarginBetweenItems
< 0) {
209 ToastContainer::iterator toast_iter
= toasts_
.find((*iter
)->id());
210 views::Widget
* widget
= NULL
;
211 if (toast_iter
!= toasts_
.end()) {
212 widget
= toast_iter
->second
->GetWidget();
213 old_toast_ids
.erase((*iter
)->id());
214 // Need to replace the contents because |view| can be updated, like
216 toast_iter
->second
->SetContents(view
);
218 ToastContentsView
* toast
= new ToastContentsView(*iter
, AsWeakPtr());
219 widget
= toast
->CreateWidget(parent_
);
220 toast
->SetContents(view
);
221 widget
->AddObserver(this);
223 toasts_
[(*iter
)->id()] = toast
;
226 // Place/move the toast widgets. Currently it stacks the widgets from the
227 // right-bottom of the work area.
228 // TODO(mukai): allow to specify the placement policy from outside of this
229 // class. The policy should be specified from preference on Windows, or
230 // the launcher alignment on ChromeOS.
232 gfx::Rect
bounds(widget
->GetWindowBoundsInScreen());
233 bounds
.set_origin(gfx::Point(left
, bottom
- bounds
.height()));
234 widget
->SetBounds(bounds
);
235 if (!widget
->IsVisible())
239 bottom
-= view_height
+ kMarginBetweenItems
;
242 for (std::set
<std::string
>::const_iterator iter
= old_toast_ids
.begin();
243 iter
!= old_toast_ids
.end(); ++iter
) {
244 ToastContainer::iterator toast_iter
= toasts_
.find(*iter
);
245 DCHECK(toast_iter
!= toasts_
.end());
246 views::Widget
* widget
= toast_iter
->second
->GetWidget();
247 widget
->RemoveObserver(this);
249 toasts_
.erase(toast_iter
);
253 void MessagePopupCollection::OnMouseEntered() {
254 for (ToastContainer::iterator iter
= toasts_
.begin();
255 iter
!= toasts_
.end(); ++iter
) {
256 iter
->second
->SuspendTimer();
260 void MessagePopupCollection::OnMouseExited() {
261 for (ToastContainer::iterator iter
= toasts_
.begin();
262 iter
!= toasts_
.end(); ++iter
) {
263 iter
->second
->RestartTimer();
267 void MessagePopupCollection::CloseAllWidgets() {
268 for (ToastContainer::iterator iter
= toasts_
.begin();
269 iter
!= toasts_
.end(); ++iter
) {
270 iter
->second
->SuspendTimer();
271 views::Widget
* widget
= iter
->second
->GetWidget();
272 widget
->RemoveObserver(this);
278 void MessagePopupCollection::OnWidgetDestroying(views::Widget
* widget
) {
279 widget
->RemoveObserver(this);
280 for (ToastContainer::iterator iter
= toasts_
.begin();
281 iter
!= toasts_
.end(); ++iter
) {
282 if (iter
->second
->GetWidget() == widget
) {
283 message_center_
->notification_list()->MarkSinglePopupAsShown(
292 } // namespace message_center