1 // Copyright 2018 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/message_popup_view.h"
6
7 #include "build/build_config.h"
8 #include "ui/accessibility/ax_enums.mojom.h"
9 #include "ui/accessibility/ax_node_data.h"
10 #include "ui/display/display.h"
11 #include "ui/display/screen.h"
12 #include "ui/message_center/public/cpp/message_center_constants.h"
13 #include "ui/message_center/views/message_popup_collection.h"
14 #include "ui/message_center/views/message_view.h"
15 #include "ui/message_center/views/message_view_factory.h"
16 #include "ui/views/layout/fill_layout.h"
17 #include "ui/views/widget/widget.h"
18
19 #if defined(OS_WIN)
20 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
21 #endif
22
23 #if defined(OS_CHROMEOS)
24 #include "ui/aura/window.h"
25 #include "ui/aura/window_targeter.h"
26 #endif
27
28 namespace message_center {
29
MessagePopupView(const Notification & notification,MessagePopupCollection * popup_collection)30 MessagePopupView::MessagePopupView(const Notification& notification,
31 MessagePopupCollection* popup_collection)
32 : message_view_(MessageViewFactory::Create(notification)),
33 popup_collection_(popup_collection),
34 a11y_feedback_on_init_(
35 notification.rich_notification_data()
36 .should_make_spoken_feedback_for_popup_updates) {
37 SetLayoutManager(std::make_unique<views::FillLayout>());
38
39 if (!message_view_->IsManuallyExpandedOrCollapsed())
40 message_view_->SetExpanded(message_view_->IsAutoExpandingAllowed());
41 AddChildView(message_view_);
42 SetNotifyEnterExitOnChild(true);
43 }
44
MessagePopupView(MessagePopupCollection * popup_collection)45 MessagePopupView::MessagePopupView(MessagePopupCollection* popup_collection)
46 : message_view_(nullptr),
47 popup_collection_(popup_collection),
48 a11y_feedback_on_init_(false) {
49 SetLayoutManager(std::make_unique<views::FillLayout>());
50 }
51
~MessagePopupView()52 MessagePopupView::~MessagePopupView() {
53 popup_collection_->NotifyPopupClosed(this);
54 }
55
UpdateContents(const Notification & notification)56 void MessagePopupView::UpdateContents(const Notification& notification) {
57 if (!IsWidgetValid())
58 return;
59 ui::AXNodeData old_data;
60 message_view_->GetAccessibleNodeData(&old_data);
61 message_view_->UpdateWithNotification(notification);
62 popup_collection_->NotifyPopupResized();
63 if (notification.rich_notification_data()
64 .should_make_spoken_feedback_for_popup_updates) {
65 ui::AXNodeData new_data;
66 message_view_->GetAccessibleNodeData(&new_data);
67
68 const std::string& new_name =
69 new_data.GetStringAttribute(ax::mojom::StringAttribute::kName);
70 const std::string& old_name =
71 old_data.GetStringAttribute(ax::mojom::StringAttribute::kName);
72 if (new_name.empty()) {
73 new_data.SetNameFrom(ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
74 return;
75 }
76
77 if (old_name != new_name)
78 NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
79 }
80 }
81
82 #if !defined(OS_APPLE)
GetOpacity() const83 float MessagePopupView::GetOpacity() const {
84 if (!IsWidgetValid())
85 return 0.f;
86 return GetWidget()->GetLayer()->opacity();
87 }
88 #endif
89
SetPopupBounds(const gfx::Rect & bounds)90 void MessagePopupView::SetPopupBounds(const gfx::Rect& bounds) {
91 if (!IsWidgetValid())
92 return;
93 GetWidget()->SetBounds(bounds);
94 }
95
SetOpacity(float opacity)96 void MessagePopupView::SetOpacity(float opacity) {
97 if (!IsWidgetValid())
98 return;
99 GetWidget()->SetOpacity(opacity);
100 }
101
AutoCollapse()102 void MessagePopupView::AutoCollapse() {
103 if (!IsWidgetValid() || is_hovered_ ||
104 message_view_->IsManuallyExpandedOrCollapsed()) {
105 return;
106 }
107 message_view_->SetExpanded(false);
108 }
109
Show()110 void MessagePopupView::Show() {
111 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
112 params.z_order = ui::ZOrderLevel::kFloatingWindow;
113 #if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_BSD)
114 // Make the widget explicitly activatable as TYPE_POPUP is not activatable by
115 // default but we need focus for the inline reply textarea.
116 params.activatable = views::Widget::InitParams::ACTIVATABLE_YES;
117 params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque;
118 #else
119 params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
120 #endif
121 params.delegate = this;
122 views::Widget* widget = new views::Widget();
123 popup_collection_->ConfigureWidgetInitParamsForContainer(widget, ¶ms);
124 widget->set_focus_on_creation(false);
125 observer_.Add(widget);
126
127 #if defined(OS_WIN)
128 // We want to ensure that this toast always goes to the native desktop,
129 // not the Ash desktop (since there is already another toast contents view
130 // there.
131 if (!params.parent)
132 params.native_widget = new views::DesktopNativeWidgetAura(widget);
133 #endif
134
135 widget->Init(std::move(params));
136
137 #if defined(OS_CHROMEOS)
138 // On Chrome OS, this widget is shown in the shelf container. It means this
139 // widget would inherit the parent's window targeter (ShelfWindowTarget) by
140 // default. But it is not good for popup. So we override it with the normal
141 // WindowTargeter.
142 gfx::NativeWindow native_window = widget->GetNativeWindow();
143 native_window->SetEventTargeter(std::make_unique<aura::WindowTargeter>());
144 #endif
145
146 widget->SetOpacity(0.0);
147 widget->ShowInactive();
148
149 if (a11y_feedback_on_init_)
150 NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
151 }
152
Close()153 void MessagePopupView::Close() {
154 if (!GetWidget()) {
155 DeleteDelegate();
156 return;
157 }
158
159 if (!GetWidget()->IsClosed())
160 GetWidget()->CloseNow();
161 }
162
OnMouseEntered(const ui::MouseEvent & event)163 void MessagePopupView::OnMouseEntered(const ui::MouseEvent& event) {
164 is_hovered_ = true;
165 popup_collection_->Update();
166 }
167
OnMouseExited(const ui::MouseEvent & event)168 void MessagePopupView::OnMouseExited(const ui::MouseEvent& event) {
169 is_hovered_ = false;
170 popup_collection_->Update();
171 }
172
ChildPreferredSizeChanged(views::View * child)173 void MessagePopupView::ChildPreferredSizeChanged(views::View* child) {
174 popup_collection_->NotifyPopupResized();
175 }
176
GetAccessibleNodeData(ui::AXNodeData * node_data)177 void MessagePopupView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
178 message_view_->GetAccessibleNodeData(node_data);
179 node_data->role = ax::mojom::Role::kAlertDialog;
180 }
181
GetClassName() const182 const char* MessagePopupView::GetClassName() const {
183 return "MessagePopupView";
184 }
185
OnDisplayChanged()186 void MessagePopupView::OnDisplayChanged() {
187 OnWorkAreaChanged();
188 }
189
OnWorkAreaChanged()190 void MessagePopupView::OnWorkAreaChanged() {
191 if (!IsWidgetValid())
192 return;
193
194 gfx::NativeView native_view = GetWidget()->GetNativeView();
195 if (!native_view)
196 return;
197
198 if (popup_collection_->RecomputeAlignment(
199 display::Screen::GetScreen()->GetDisplayNearestView(native_view))) {
200 popup_collection_->ResetBounds();
201 }
202 }
203
OnFocus()204 void MessagePopupView::OnFocus() {
205 // This view is just a container, so advance focus to the underlying
206 // MessageView.
207 GetFocusManager()->SetFocusedView(message_view_);
208 }
209
OnWidgetActivationChanged(views::Widget * widget,bool active)210 void MessagePopupView::OnWidgetActivationChanged(views::Widget* widget,
211 bool active) {
212 is_active_ = active;
213 popup_collection_->Update();
214 }
215
OnWidgetDestroyed(views::Widget * widget)216 void MessagePopupView::OnWidgetDestroyed(views::Widget* widget) {
217 observer_.Remove(widget);
218 }
219
IsWidgetValid() const220 bool MessagePopupView::IsWidgetValid() const {
221 return GetWidget() && !GetWidget()->IsClosed();
222 }
223
224 } // namespace message_center
225