Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / ui / message_center / views / message_popup_collection.cc
blob71a73c5d14abde66a1290b7d616f63179ece94fe
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"
7 #include <set>
9 #include "base/bind.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 {
27 public:
28 ToastContentsView(const Notification* notification,
29 base::WeakPtr<MessagePopupCollection> collection)
30 : collection_(collection) {
31 DCHECK(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;
53 if (parent)
54 params.parent = parent;
55 else
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);
61 widget->Init(params);
62 return widget;
65 void SetContents(MessageView* view) {
66 RemoveAllChildViews(true);
67 AddChildView(view);
68 views::Widget* widget = GetWidget();
69 if (widget) {
70 gfx::Rect bounds = widget->GetWindowBoundsInScreen();
71 bounds.set_width(kNotificationWidth);
72 bounds.set_height(view->GetHeightForWidth(kNotificationWidth));
73 widget->SetBounds(bounds);
75 Layout();
78 void SuspendTimer() {
79 if (timer_.get())
80 timer_->Stop();
83 void RestartTimer() {
84 if (!timer_.get())
85 return;
87 delay_ -= base::Time::Now() - start_time_;
88 if (delay_ < base::TimeDelta())
89 GetWidget()->Close();
90 else
91 StartTimer();
94 void StartTimer() {
95 if (!timer_.get())
96 return;
98 start_time_ = base::Time::Now();
99 timer_->Start(FROM_HERE,
100 delay_,
101 base::Bind(&views::Widget::Close,
102 base::Unretained(GetWidget())));
105 // Overridden from views::WidgetDelegate:
106 virtual views::View* GetContentsView() OVERRIDE {
107 return this;
110 virtual void WindowClosing() OVERRIDE {
111 if (timer_.get() && timer_->IsRunning())
112 SuspendTimer();
115 virtual bool CanActivate() const OVERRIDE {
116 #if defined(OS_WIN) && defined(USE_AURA)
117 return true;
118 #else
119 return false;
120 #endif
123 // Overridden from views::View:
124 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE {
125 if (collection_)
126 collection_->OnMouseEntered();
129 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE {
130 if (collection_)
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)
141 return gfx::Size();
143 return gfx::Size(kNotificationWidth,
144 child_at(0)->GetHeightForWidth(kNotificationWidth));
147 private:
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)
158 : parent_(parent),
159 message_center_(message_center) {
160 DCHECK(message_center_);
161 UpdatePopups();
164 MessagePopupCollection::~MessagePopupCollection() {
165 CloseAllWidgets();
168 void MessagePopupCollection::UpdatePopups() {
169 NotificationList::PopupNotifications popups =
170 message_center_->notification_list()->GetPopupNotifications();
172 if (popups.empty()) {
173 CloseAllWidgets();
174 return;
177 gfx::Rect work_area;
178 if (!parent_) {
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();
184 } else {
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();
191 ++iter) {
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) {
201 MessageView* view =
202 NotificationView::Create(*(*iter), message_center_, true);
203 int view_height = view->GetHeightForWidth(kNotificationWidth);
204 if (bottom - view_height - kMarginBetweenItems < 0) {
205 delete view;
206 break;
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
215 // image loads.
216 toast_iter->second->SetContents(view);
217 } else {
218 ToastContentsView* toast = new ToastContentsView(*iter, AsWeakPtr());
219 widget = toast->CreateWidget(parent_);
220 toast->SetContents(view);
221 widget->AddObserver(this);
222 toast->StartTimer();
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.
231 if (widget) {
232 gfx::Rect bounds(widget->GetWindowBoundsInScreen());
233 bounds.set_origin(gfx::Point(left, bottom - bounds.height()));
234 widget->SetBounds(bounds);
235 if (!widget->IsVisible())
236 widget->Show();
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);
248 widget->Close();
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);
273 widget->Close();
275 toasts_.clear();
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(
284 iter->first, false);
285 toasts_.erase(iter);
286 break;
289 UpdatePopups();
292 } // namespace message_center