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