// Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/message_center/views/message_popup_view.h" #include "build/build_config.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_data.h" #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/message_center/public/cpp/message_center_constants.h" #include "ui/message_center/views/message_popup_collection.h" #include "ui/message_center/views/message_view.h" #include "ui/message_center/views/message_view_factory.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/widget/widget.h" #if defined(OS_WIN) #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" #endif #if defined(OS_CHROMEOS) #include "ui/aura/window.h" #include "ui/aura/window_targeter.h" #endif namespace message_center { MessagePopupView::MessagePopupView(const Notification& notification, MessagePopupCollection* popup_collection) : message_view_(MessageViewFactory::Create(notification)), popup_collection_(popup_collection), a11y_feedback_on_init_( notification.rich_notification_data() .should_make_spoken_feedback_for_popup_updates) { SetLayoutManager(std::make_unique()); if (!message_view_->IsManuallyExpandedOrCollapsed()) message_view_->SetExpanded(message_view_->IsAutoExpandingAllowed()); AddChildView(message_view_); SetNotifyEnterExitOnChild(true); } MessagePopupView::MessagePopupView(MessagePopupCollection* popup_collection) : message_view_(nullptr), popup_collection_(popup_collection), a11y_feedback_on_init_(false) { SetLayoutManager(std::make_unique()); } MessagePopupView::~MessagePopupView() { popup_collection_->NotifyPopupClosed(this); } void MessagePopupView::UpdateContents(const Notification& notification) { if (!IsWidgetValid()) return; ui::AXNodeData old_data; message_view_->GetAccessibleNodeData(&old_data); message_view_->UpdateWithNotification(notification); popup_collection_->NotifyPopupResized(); if (notification.rich_notification_data() .should_make_spoken_feedback_for_popup_updates) { ui::AXNodeData new_data; message_view_->GetAccessibleNodeData(&new_data); const std::string& new_name = new_data.GetStringAttribute(ax::mojom::StringAttribute::kName); const std::string& old_name = old_data.GetStringAttribute(ax::mojom::StringAttribute::kName); if (new_name.empty()) { new_data.SetNameFrom(ax::mojom::NameFrom::kAttributeExplicitlyEmpty); return; } if (old_name != new_name) NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true); } } #if !defined(OS_APPLE) float MessagePopupView::GetOpacity() const { if (!IsWidgetValid()) return 0.f; return GetWidget()->GetLayer()->opacity(); } #endif void MessagePopupView::SetPopupBounds(const gfx::Rect& bounds) { if (!IsWidgetValid()) return; GetWidget()->SetBounds(bounds); } void MessagePopupView::SetOpacity(float opacity) { if (!IsWidgetValid()) return; GetWidget()->SetOpacity(opacity); } void MessagePopupView::AutoCollapse() { if (!IsWidgetValid() || is_hovered_ || message_view_->IsManuallyExpandedOrCollapsed()) { return; } message_view_->SetExpanded(false); } void MessagePopupView::Show() { views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); params.z_order = ui::ZOrderLevel::kFloatingWindow; #if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_BSD) // Make the widget explicitly activatable as TYPE_POPUP is not activatable by // default but we need focus for the inline reply textarea. params.activatable = views::Widget::InitParams::ACTIVATABLE_YES; params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque; #else params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; #endif params.delegate = this; views::Widget* widget = new views::Widget(); popup_collection_->ConfigureWidgetInitParamsForContainer(widget, ¶ms); widget->set_focus_on_creation(false); observer_.Add(widget); #if defined(OS_WIN) // We want to ensure that this toast always goes to the native desktop, // not the Ash desktop (since there is already another toast contents view // there. if (!params.parent) params.native_widget = new views::DesktopNativeWidgetAura(widget); #endif widget->Init(std::move(params)); #if defined(OS_CHROMEOS) // On Chrome OS, this widget is shown in the shelf container. It means this // widget would inherit the parent's window targeter (ShelfWindowTarget) by // default. But it is not good for popup. So we override it with the normal // WindowTargeter. gfx::NativeWindow native_window = widget->GetNativeWindow(); native_window->SetEventTargeter(std::make_unique()); #endif widget->SetOpacity(0.0); widget->ShowInactive(); if (a11y_feedback_on_init_) NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true); } void MessagePopupView::Close() { if (!GetWidget()) { DeleteDelegate(); return; } if (!GetWidget()->IsClosed()) GetWidget()->CloseNow(); } void MessagePopupView::OnMouseEntered(const ui::MouseEvent& event) { is_hovered_ = true; popup_collection_->Update(); } void MessagePopupView::OnMouseExited(const ui::MouseEvent& event) { is_hovered_ = false; popup_collection_->Update(); } void MessagePopupView::ChildPreferredSizeChanged(views::View* child) { popup_collection_->NotifyPopupResized(); } void MessagePopupView::GetAccessibleNodeData(ui::AXNodeData* node_data) { message_view_->GetAccessibleNodeData(node_data); node_data->role = ax::mojom::Role::kAlertDialog; } const char* MessagePopupView::GetClassName() const { return "MessagePopupView"; } void MessagePopupView::OnDisplayChanged() { OnWorkAreaChanged(); } void MessagePopupView::OnWorkAreaChanged() { if (!IsWidgetValid()) return; gfx::NativeView native_view = GetWidget()->GetNativeView(); if (!native_view) return; if (popup_collection_->RecomputeAlignment( display::Screen::GetScreen()->GetDisplayNearestView(native_view))) { popup_collection_->ResetBounds(); } } void MessagePopupView::OnFocus() { // This view is just a container, so advance focus to the underlying // MessageView. GetFocusManager()->SetFocusedView(message_view_); } void MessagePopupView::OnWidgetActivationChanged(views::Widget* widget, bool active) { is_active_ = active; popup_collection_->Update(); } void MessagePopupView::OnWidgetDestroyed(views::Widget* widget) { observer_.Remove(widget); } bool MessagePopupView::IsWidgetValid() const { return GetWidget() && !GetWidget()->IsClosed(); } } // namespace message_center