Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ui / message_center / views / toast_contents_view.cc
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 "ui/message_center/views/toast_contents_view.h"
6
7 #include "base/bind.h"
8 #include "base/compiler_specific.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/memory/weak_ptr.h"
11 #include "base/time/time.h"
12 #include "ui/accessibility/ax_view_state.h"
13 #include "ui/gfx/animation/animation_delegate.h"
14 #include "ui/gfx/animation/slide_animation.h"
15 #include "ui/gfx/display.h"
16 #include "ui/gfx/screen.h"
17 #include "ui/message_center/message_center_style.h"
18 #include "ui/message_center/notification.h"
19 #include "ui/message_center/views/message_popup_collection.h"
20 #include "ui/message_center/views/message_view.h"
21 #include "ui/views/background.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/widget.h"
24 #include "ui/views/widget/widget_delegate.h"
25
26 #if defined(OS_WIN)
27 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
28 #endif
29
30 using gfx::Screen;
31
32 namespace message_center {
33 namespace {
34
35 // The width of a toast before animated reveal and after closing.
36 const int kClosedToastWidth = 5;
37
38 // FadeIn/Out look a bit better if they are slightly longer then default slide.
39 const int kFadeInOutDuration = 200;
40
41 }  // namespace.
42
43 // static
44 gfx::Size ToastContentsView::GetToastSizeForView(const views::View* view) {
45   int width = kNotificationWidth + view->GetInsets().width();
46   return gfx::Size(width, view->GetHeightForWidth(width));
47 }
48
49 ToastContentsView::ToastContentsView(
50     const std::string& notification_id,
51     base::WeakPtr<MessagePopupCollection> collection)
52     : collection_(collection),
53       id_(notification_id),
54       is_animating_bounds_(false),
55       is_closing_(false),
56       closing_animation_(NULL) {
57   set_notify_enter_exit_on_child(true);
58   // Sets the transparent background. Then, when the message view is slid out,
59   // the whole toast seems to slide although the actual bound of the widget
60   // remains. This is hacky but easier to keep the consistency.
61   set_background(views::Background::CreateSolidBackground(0, 0, 0, 0));
62
63   fade_animation_.reset(new gfx::SlideAnimation(this));
64   fade_animation_->SetSlideDuration(kFadeInOutDuration);
65
66   CreateWidget(collection->parent());
67 }
68
69 // This is destroyed when the toast window closes.
70 ToastContentsView::~ToastContentsView() {
71   if (collection_)
72     collection_->ForgetToast(this);
73 }
74
75 void ToastContentsView::SetContents(MessageView* view,
76                                     bool a11y_feedback_for_updates) {
77   bool already_has_contents = child_count() > 0;
78   RemoveAllChildViews(true);
79   AddChildView(view);
80   preferred_size_ = GetToastSizeForView(view);
81   Layout();
82
83   // If it has the contents already, this invocation means an update of the
84   // popup toast, and the new contents should be read through a11y feature.
85   // The notification type should be ALERT, otherwise the accessibility message
86   // won't be read for this view which returns ROLE_WINDOW.
87   if (already_has_contents && a11y_feedback_for_updates)
88     NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, false);
89 }
90
91 void ToastContentsView::UpdateContents(const Notification& notification,
92                                        bool a11y_feedback_for_updates) {
93   DCHECK_GT(child_count(), 0);
94   MessageView* message_view = static_cast<MessageView*>(child_at(0));
95   message_view->UpdateWithNotification(notification);
96   if (a11y_feedback_for_updates)
97     NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, false);
98 }
99
100 void ToastContentsView::RevealWithAnimation(gfx::Point origin) {
101   // Place/move the toast widgets. Currently it stacks the widgets from the
102   // right-bottom of the work area.
103   // TODO(mukai): allow to specify the placement policy from outside of this
104   // class. The policy should be specified from preference on Windows, or
105   // the launcher alignment on ChromeOS.
106   origin_ = gfx::Point(origin.x() - preferred_size_.width(),
107                        origin.y() - preferred_size_.height());
108
109   gfx::Rect stable_bounds(origin_, preferred_size_);
110
111   SetBoundsInstantly(GetClosedToastBounds(stable_bounds));
112   StartFadeIn();
113   SetBoundsWithAnimation(stable_bounds);
114 }
115
116 void ToastContentsView::CloseWithAnimation() {
117   if (is_closing_)
118     return;
119   is_closing_ = true;
120   StartFadeOut();
121 }
122
123 void ToastContentsView::SetBoundsInstantly(gfx::Rect new_bounds) {
124   if (new_bounds == bounds())
125     return;
126
127   origin_ = new_bounds.origin();
128   if (!GetWidget())
129     return;
130   GetWidget()->SetBounds(new_bounds);
131 }
132
133 void ToastContentsView::SetBoundsWithAnimation(gfx::Rect new_bounds) {
134   if (new_bounds == bounds())
135     return;
136
137   origin_ = new_bounds.origin();
138   if (!GetWidget())
139     return;
140
141   // This picks up the current bounds, so if there was a previous animation
142   // half-done, the next one will pick up from the current location.
143   // This is the only place that should query current location of the Widget
144   // on screen, the rest should refer to the bounds_.
145   animated_bounds_start_ = GetWidget()->GetWindowBoundsInScreen();
146   animated_bounds_end_ = new_bounds;
147
148   if (collection_)
149     collection_->IncrementDeferCounter();
150
151   if (bounds_animation_.get())
152     bounds_animation_->Stop();
153
154   bounds_animation_.reset(new gfx::SlideAnimation(this));
155   bounds_animation_->Show();
156 }
157
158 void ToastContentsView::StartFadeIn() {
159   // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
160   if (collection_)
161     collection_->IncrementDeferCounter();
162   fade_animation_->Stop();
163
164   GetWidget()->SetOpacity(0);
165   GetWidget()->ShowInactive();
166   fade_animation_->Reset(0);
167   fade_animation_->Show();
168 }
169
170 void ToastContentsView::StartFadeOut() {
171   // The decrement is done in OnBoundsAnimationEndedOrCancelled callback.
172   if (collection_)
173     collection_->IncrementDeferCounter();
174   fade_animation_->Stop();
175
176   closing_animation_ = (is_closing_ ? fade_animation_.get() : NULL);
177   fade_animation_->Reset(1);
178   fade_animation_->Hide();
179 }
180
181 void ToastContentsView::OnBoundsAnimationEndedOrCancelled(
182     const gfx::Animation* animation) {
183   if (is_closing_ && closing_animation_ == animation && GetWidget()) {
184     views::Widget* widget = GetWidget();
185
186     // TODO(dewittj): This is a workaround to prevent a nasty bug where
187     // closing a transparent widget doesn't actually remove the window,
188     // causing entire areas of the screen to become unresponsive to clicks.
189     // See crbug.com/243469
190     widget->Hide();
191 #if defined(OS_WIN)
192     widget->SetOpacity(0xFF);
193 #endif
194
195     widget->Close();
196   }
197
198   // This cannot be called before GetWidget()->Close(). Decrementing defer count
199   // will invoke update, which may invoke another close animation with
200   // incrementing defer counter. Close() after such process will cause a
201   // mismatch between increment/decrement. See crbug.com/238477
202   if (collection_)
203     collection_->DecrementDeferCounter();
204 }
205
206 // gfx::AnimationDelegate
207 void ToastContentsView::AnimationProgressed(const gfx::Animation* animation) {
208   if (animation == bounds_animation_.get()) {
209     gfx::Rect current(animation->CurrentValueBetween(
210         animated_bounds_start_, animated_bounds_end_));
211     GetWidget()->SetBounds(current);
212   } else if (animation == fade_animation_.get()) {
213     unsigned char opacity =
214         static_cast<unsigned char>(fade_animation_->GetCurrentValue() * 255);
215     GetWidget()->SetOpacity(opacity);
216   }
217 }
218
219 void ToastContentsView::AnimationEnded(const gfx::Animation* animation) {
220   OnBoundsAnimationEndedOrCancelled(animation);
221 }
222
223 void ToastContentsView::AnimationCanceled(
224     const gfx::Animation* animation) {
225   OnBoundsAnimationEndedOrCancelled(animation);
226 }
227
228 // views::WidgetDelegate
229 views::View* ToastContentsView::GetContentsView() {
230   return this;
231 }
232
233 void ToastContentsView::WindowClosing() {
234   if (!is_closing_ && collection_.get())
235     collection_->ForgetToast(this);
236 }
237
238 void ToastContentsView::OnDisplayChanged() {
239   views::Widget* widget = GetWidget();
240   if (!widget)
241     return;
242
243   gfx::NativeView native_view = widget->GetNativeView();
244   if (!native_view || !collection_.get())
245     return;
246
247   collection_->OnDisplayMetricsChanged(
248       Screen::GetScreenFor(native_view)->GetDisplayNearestWindow(native_view));
249 }
250
251 void ToastContentsView::OnWorkAreaChanged() {
252   views::Widget* widget = GetWidget();
253   if (!widget)
254     return;
255
256   gfx::NativeView native_view = widget->GetNativeView();
257   if (!native_view || !collection_.get())
258     return;
259
260   collection_->OnDisplayMetricsChanged(
261       Screen::GetScreenFor(native_view)->GetDisplayNearestWindow(native_view));
262 }
263
264 // views::View
265 void ToastContentsView::OnMouseEntered(const ui::MouseEvent& event) {
266   if (collection_)
267     collection_->OnMouseEntered(this);
268 }
269
270 void ToastContentsView::OnMouseExited(const ui::MouseEvent& event) {
271   if (collection_)
272     collection_->OnMouseExited(this);
273 }
274
275 void ToastContentsView::Layout() {
276   if (child_count() > 0) {
277     child_at(0)->SetBounds(
278         0, 0, preferred_size_.width(), preferred_size_.height());
279   }
280 }
281
282 gfx::Size ToastContentsView::GetPreferredSize() const {
283   return child_count() ? GetToastSizeForView(child_at(0)) : gfx::Size();
284 }
285
286 void ToastContentsView::GetAccessibleState(ui::AXViewState* state) {
287   if (child_count() > 0)
288     child_at(0)->GetAccessibleState(state);
289   state->role = ui::AX_ROLE_WINDOW;
290 }
291
292 void ToastContentsView::ClickOnNotification(
293     const std::string& notification_id) {
294   if (collection_)
295     collection_->ClickOnNotification(notification_id);
296 }
297
298 void ToastContentsView::RemoveNotification(
299     const std::string& notification_id,
300     bool by_user) {
301   if (collection_)
302     collection_->RemoveNotification(notification_id, by_user);
303 }
304
305 scoped_ptr<ui::MenuModel> ToastContentsView::CreateMenuModel(
306       const NotifierId& notifier_id,
307       const base::string16& display_source) {
308   // Should not reach, the context menu should be handled in
309   // MessagePopupCollection.
310   NOTREACHED();
311   return scoped_ptr<ui::MenuModel>();
312 }
313
314 bool ToastContentsView::HasClickedListener(
315     const std::string& notification_id) {
316   if (!collection_)
317     return false;
318   return collection_->HasClickedListener(notification_id);
319 }
320
321 void ToastContentsView::ClickOnNotificationButton(
322     const std::string& notification_id,
323     int button_index) {
324   if (collection_)
325     collection_->ClickOnNotificationButton(notification_id, button_index);
326 }
327
328 void ToastContentsView::CreateWidget(gfx::NativeView parent) {
329   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
330   params.keep_on_top = true;
331   if (parent)
332     params.parent = parent;
333   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
334   params.delegate = this;
335   views::Widget* widget = new views::Widget();
336   widget->set_focus_on_creation(false);
337
338 #if defined(OS_WIN)
339   // We want to ensure that this toast always goes to the native desktop,
340   // not the Ash desktop (since there is already another toast contents view
341   // there.
342   if (!params.parent)
343     params.native_widget = new views::DesktopNativeWidgetAura(widget);
344 #endif
345
346   widget->Init(params);
347 }
348
349 gfx::Rect ToastContentsView::GetClosedToastBounds(gfx::Rect bounds) {
350   return gfx::Rect(bounds.x() + bounds.width() - kClosedToastWidth,
351                    bounds.y(),
352                    kClosedToastWidth,
353                    bounds.height());
354 }
355
356 }  // namespace message_center