#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include <algorithm>
#include <set>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/base/class_property.h"
#include "ui/base/default_style.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_manager.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_element.h"
#include "ui/compositor/layer_animator.h"
#include "ui/display/screen.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/vector2d_conversions.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/layout/layout_manager.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/views_features.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
#include "ui/views/window/dialog_client_view.h"
#if BUILDFLAG(IS_WIN)
#include "ui/base/win/shell.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "ui/views/widget/widget_utils_mac.h"
#else
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#endif
DEFINE_UI_CLASS_PROPERTY_TYPE(std::vector<views::BubbleDialogDelegate*>*)
namespace views {
namespace {
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(std::vector<views::BubbleDialogDelegate*>,
kAnchorVector,
nullptr)
std::vector<BubbleDialogDelegate*>& GetAnchorVector(View* view) {
if (!view->GetProperty(kAnchorVector)) {
return *(view->SetProperty(
kAnchorVector, std::make_unique<std::vector<BubbleDialogDelegate*>>()));
}
return *(view->GetProperty(kAnchorVector));
}
class BubbleWidget : public Widget {
public:
BubbleWidget() = default;
BubbleWidget(const BubbleWidget&) = delete;
BubbleWidget& operator=(const BubbleWidget&) = delete;
const ui::ThemeProvider* GetThemeProvider() const override {
const Widget* const anchor = GetAnchorWidget();
return anchor ? anchor->GetThemeProvider() : Widget::GetThemeProvider();
}
ui::ColorProviderManager::ThemeInitializerSupplier* GetCustomTheme()
const override {
const Widget* const anchor = GetAnchorWidget();
return anchor ? anchor->GetCustomTheme() : Widget::GetCustomTheme();
}
const ui::NativeTheme* GetNativeTheme() const override {
const Widget* const anchor = GetAnchorWidget();
return anchor ? anchor->GetNativeTheme() : Widget::GetNativeTheme();
}
Widget* GetPrimaryWindowWidget() override {
Widget* const anchor = GetAnchorWidget();
return anchor ? anchor->GetPrimaryWindowWidget()
: Widget::GetPrimaryWindowWidget();
}
private:
const Widget* GetAnchorWidget() const {
BubbleDialogDelegate* const bubble_delegate =
static_cast<BubbleDialogDelegate*>(widget_delegate());
return bubble_delegate ? bubble_delegate->anchor_widget() : nullptr;
}
Widget* GetAnchorWidget() {
return const_cast<Widget*>(std::as_const(*this).GetAnchorWidget());
}
};
class BubbleDialogFrameView : public BubbleFrameView {
public:
explicit BubbleDialogFrameView(const gfx::Insets& title_margins)
: BubbleFrameView(title_margins, gfx::Insets()) {}
BubbleDialogFrameView(const BubbleDialogFrameView&) = delete;
BubbleDialogFrameView& operator=(const BubbleDialogFrameView&) = delete;
gfx::Size GetMinimumSize() const override { return gfx::Size(); }
gfx::Size GetMaximumSize() const override { return gfx::Size(); }
};
Widget* CreateBubbleWidget(BubbleDialogDelegate* bubble) {
DCHECK(bubble->owned_by_widget());
Widget* bubble_widget = new BubbleWidget();
Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE);
bubble_params.delegate = bubble;
bubble_params.opacity = Widget::InitParams::WindowOpacity::kTranslucent;
bubble_params.accept_events = bubble->accept_events();
bubble_params.remove_standard_frame = true;
bubble_params.layer_type = bubble->GetLayerType();
if (bubble->GetShadow() == BubbleBorder::NO_SHADOW)
bubble_params.shadow_type = Widget::InitParams::ShadowType::kDefault;
else
bubble_params.shadow_type = Widget::InitParams::ShadowType::kNone;
#if BUILDFLAG(IS_CHROMEOS_ASH)
bubble_params.background_elevation =
ui::ColorProviderManager::ElevationMode::kHigh;
#endif
gfx::NativeView parent = nullptr;
if (bubble->has_parent()) {
if (bubble->parent_window()) {
parent = bubble->parent_window();
} else if (bubble->anchor_widget()) {
parent = bubble->anchor_widget()->GetNativeView();
}
}
bubble_params.parent = parent;
bubble_params.activatable = bubble->CanActivate()
? Widget::InitParams::Activatable::kYes
: Widget::InitParams::Activatable::kNo;
bubble->OnBeforeBubbleWidgetInit(&bubble_params, bubble_widget);
DCHECK(bubble_params.parent || !bubble->has_parent());
bubble_widget->Init(std::move(bubble_params));
#if !BUILDFLAG(IS_MAC)
if (!base::FeatureList::IsEnabled(views::features::kWidgetLayering)) {
if (bubble->has_parent() && parent)
bubble_widget->StackAbove(parent);
}
#endif
return bubble_widget;
}
}
class BubbleDialogDelegate::AnchorViewObserver : public ViewObserver {
public:
AnchorViewObserver(BubbleDialogDelegate* parent, View* anchor_view)
: parent_(parent), anchor_view_(anchor_view) {
anchor_view_->AddObserver(this);
AddToAnchorVector();
}
AnchorViewObserver(const AnchorViewObserver&) = delete;
AnchorViewObserver& operator=(const AnchorViewObserver&) = delete;
~AnchorViewObserver() override {
RemoveFromAnchorVector();
anchor_view_->RemoveObserver(this);
}
View* anchor_view() const { return anchor_view_; }
void OnViewIsDeleting(View* observed_view) override {
DCHECK_EQ(anchor_view_, observed_view);
parent_->SetAnchorView(nullptr);
}
void OnViewBoundsChanged(View* observed_view) override {
DCHECK_EQ(anchor_view_, observed_view);
parent_->OnAnchorBoundsChanged();
}
private:
void AddToAnchorVector() {
auto& vector = GetAnchorVector(anchor_view_);
DCHECK(!base::Contains(vector, parent_));
vector.push_back(parent_);
}
void RemoveFromAnchorVector() {
auto& vector = GetAnchorVector(anchor_view_);
DCHECK(!vector.empty());
auto iter = vector.cend() - 1;
bool latest_anchor_bubble_will_change = (*iter == parent_);
if (!latest_anchor_bubble_will_change)
iter = std::find(vector.cbegin(), iter, parent_);
DCHECK(iter != vector.cend());
vector.erase(iter);
if (!vector.empty() && latest_anchor_bubble_will_change)
vector.back()->NotifyAnchoredBubbleIsPrimary();
}
const raw_ptr<BubbleDialogDelegate> parent_;
const raw_ptr<View> anchor_view_;
};
#if BUILDFLAG(IS_MAC)
class BubbleDialogDelegate::AnchorWidgetObserver : public WidgetObserver {
#else
class BubbleDialogDelegate::AnchorWidgetObserver : public WidgetObserver,
public aura::WindowObserver {
#endif
public:
AnchorWidgetObserver(BubbleDialogDelegate* owner, Widget* widget)
: owner_(owner) {
widget_observation_.Observe(widget);
#if !BUILDFLAG(IS_MAC)
window_observation_.Observe(widget->GetNativeWindow());
#endif
}
~AnchorWidgetObserver() override = default;
void OnWidgetDestroying(Widget* widget) override {
#if !BUILDFLAG(IS_MAC)
DCHECK(window_observation_.IsObservingSource(widget->GetNativeWindow()));
window_observation_.Reset();
#endif
DCHECK(widget_observation_.IsObservingSource(widget));
widget_observation_.Reset();
owner_->OnAnchorWidgetDestroying();
}
void OnWidgetActivationChanged(Widget* widget, bool active) override {
owner_->OnWidgetActivationChanged(widget, active);
}
void OnWidgetBoundsChanged(Widget* widget, const gfx::Rect&) override {
owner_->OnAnchorBoundsChanged();
}
#if !BUILDFLAG(IS_MAC)
void OnWindowTransformed(aura::Window* window,
ui::PropertyChangeReason reason) override {
if (window->is_destroying())
return;
if (!window->layer()->GetAnimator()->IsAnimatingOnePropertyOf(
ui::LayerAnimationElement::TRANSFORM)) {
owner_->OnAnchorBoundsChanged();
}
}
void OnWindowDestroying(aura::Window* window) override {
window_observation_.Reset();
}
#endif
private:
raw_ptr<BubbleDialogDelegate> owner_;
base::ScopedObservation<views::Widget, views::WidgetObserver>
widget_observation_{this};
#if !BUILDFLAG(IS_MAC)
base::ScopedObservation<aura::Window, aura::WindowObserver>
window_observation_{this};
#endif
};
class BubbleDialogDelegate::BubbleWidgetObserver : public WidgetObserver {
public:
BubbleWidgetObserver(BubbleDialogDelegate* owner, Widget* widget)
: owner_(owner) {
observation_.Observe(widget);
}
~BubbleWidgetObserver() override = default;
void OnWidgetClosing(Widget* widget) override {
owner_->OnBubbleWidgetClosing();
owner_->OnWidgetClosing(widget);
}
void OnWidgetDestroying(Widget* widget) override {
owner_->OnWidgetDestroying(widget);
}
void OnWidgetDestroyed(Widget* widget) override {
DCHECK(observation_.IsObservingSource(widget));
observation_.Reset();
owner_->OnWidgetDestroyed(widget);
}
void OnWidgetBoundsChanged(Widget* widget, const gfx::Rect& bounds) override {
owner_->OnWidgetBoundsChanged(widget, bounds);
}
void OnWidgetVisibilityChanged(Widget* widget, bool visible) override {
owner_->OnBubbleWidgetVisibilityChanged(visible);
owner_->OnWidgetVisibilityChanged(widget, visible);
}
void OnWidgetActivationChanged(Widget* widget, bool active) override {
owner_->OnBubbleWidgetActivationChanged(active);
owner_->OnWidgetActivationChanged(widget, active);
}
private:
const raw_ptr<BubbleDialogDelegate> owner_;
base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
this};
};
class BubbleDialogDelegate::ThemeObserver : public ViewObserver {
public:
explicit ThemeObserver(BubbleDialogDelegate* delegate) : delegate_(delegate) {
observation_.Observe(delegate->GetContentsView());
}
void OnViewThemeChanged(views::View* view) override {
delegate_->UpdateColorsFromTheme();
}
private:
const raw_ptr<BubbleDialogDelegate> delegate_;
base::ScopedObservation<View, ViewObserver> observation_{this};
};
class BubbleDialogDelegateView::CloseOnDeactivatePin::Pins {
public:
Pins() = default;
~Pins() = default;
bool is_pinned() const { return !pins_.empty(); }
base::WeakPtr<Pins> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
void AddPin(CloseOnDeactivatePin* pin) {
const auto result = pins_.insert(pin);
DCHECK(result.second);
}
void RemovePin(CloseOnDeactivatePin* pin) {
const auto result = pins_.erase(pin);
DCHECK(result);
}
protected:
std::set<CloseOnDeactivatePin*> pins_;
base::WeakPtrFactory<Pins> weak_ptr_factory_{this};
};
BubbleDialogDelegate::CloseOnDeactivatePin::CloseOnDeactivatePin(
base::WeakPtr<Pins> pins)
: pins_(pins) {
pins_->AddPin(this);
}
BubbleDialogDelegate::CloseOnDeactivatePin::~CloseOnDeactivatePin() {
Pins* const pins = pins_.get();
if (pins)
pins->RemovePin(this);
}
BubbleDialogDelegate::BubbleDialogDelegate(View* anchor_view,
BubbleBorder::Arrow arrow,
BubbleBorder::Shadow shadow)
: arrow_(arrow),
shadow_(shadow),
close_on_deactivate_pins_(
std::make_unique<CloseOnDeactivatePin::Pins>()) {
SetOwnedByWidget(true);
SetAnchorView(anchor_view);
SetArrow(arrow);
SetShowCloseButton(false);
LayoutProvider* const layout_provider = LayoutProvider::Get();
set_margins(layout_provider->GetDialogInsetsForContentType(
DialogContentType::kText, DialogContentType::kText));
set_title_margins(layout_provider->GetInsetsMetric(INSETS_DIALOG_TITLE));
set_footnote_margins(
layout_provider->GetInsetsMetric(INSETS_DIALOG_SUBSECTION));
RegisterWidgetInitializedCallback(base::BindOnce(
[](BubbleDialogDelegate* bubble_delegate) {
bubble_delegate->theme_observer_ =
std::make_unique<ThemeObserver>(bubble_delegate);
bubble_delegate->UpdateColorsFromTheme();
},
this));
}
BubbleDialogDelegate::~BubbleDialogDelegate() {
SetAnchorView(nullptr);
}
Widget* BubbleDialogDelegate::CreateBubble(
std::unique_ptr<BubbleDialogDelegate> bubble_delegate_unique) {
BubbleDialogDelegate* const bubble_delegate = bubble_delegate_unique.get();
DCHECK(bubble_delegate->owned_by_widget());
if (bubble_delegate->GetModalType() == ui::MODAL_TYPE_WINDOW) {
DCHECK(!bubble_delegate->GetAnchorView());
DCHECK_EQ(bubble_delegate->GetAnchorRect(), gfx::Rect());
}
bubble_delegate->Init();
bubble_delegate->SetAnchorView(bubble_delegate->GetAnchorView());
Widget* const bubble_widget =
CreateBubbleWidget(bubble_delegate_unique.release());
bubble_delegate->set_adjust_if_offscreen(
PlatformStyle::kAdjustBubbleIfOffscreen);
bubble_delegate->SizeToContents();
bubble_delegate->bubble_widget_observer_ =
std::make_unique<BubbleWidgetObserver>(bubble_delegate, bubble_widget);
return bubble_widget;
}
Widget* BubbleDialogDelegateView::CreateBubble(
std::unique_ptr<BubbleDialogDelegateView> delegate) {
return BubbleDialogDelegate::CreateBubble(std::move(delegate));
}
Widget* BubbleDialogDelegateView::CreateBubble(BubbleDialogDelegateView* view) {
return CreateBubble(base::WrapUnique(view));
}
BubbleDialogDelegateView::BubbleDialogDelegateView()
: BubbleDialogDelegateView(nullptr, BubbleBorder::TOP_LEFT) {}
BubbleDialogDelegateView::BubbleDialogDelegateView(View* anchor_view,
BubbleBorder::Arrow arrow,
BubbleBorder::Shadow shadow)
: BubbleDialogDelegate(anchor_view, arrow, shadow) {}
BubbleDialogDelegateView::~BubbleDialogDelegateView() {
SetLayoutManager(nullptr);
SetAnchorView(nullptr);
}
BubbleDialogDelegate* BubbleDialogDelegate::AsBubbleDialogDelegate() {
return this;
}
std::unique_ptr<NonClientFrameView>
BubbleDialogDelegate::CreateNonClientFrameView(Widget* widget) {
auto frame = std::make_unique<BubbleDialogFrameView>(title_margins_);
frame->SetFootnoteMargins(footnote_margins_);
frame->SetFootnoteView(DisownFootnoteView());
std::unique_ptr<BubbleBorder> border =
std::make_unique<BubbleBorder>(arrow(), GetShadow());
border->SetColor(color());
if (GetParams().round_corners) {
border->SetCornerRadius(GetCornerRadius());
}
frame->SetBubbleBorder(std::move(border));
return frame;
}
ClientView* BubbleDialogDelegate::CreateClientView(Widget* widget) {
client_view_ = DialogDelegate::CreateClientView(widget);
if (paint_client_to_layer_) {
client_view_->SetPaintToLayer();
client_view_->layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF(GetCornerRadius()));
client_view_->layer()->SetIsFastRoundedCorner(true);
}
return client_view_;
}
Widget* BubbleDialogDelegateView::GetWidget() {
return View::GetWidget();
}
const Widget* BubbleDialogDelegateView::GetWidget() const {
return View::GetWidget();
}
View* BubbleDialogDelegateView::GetContentsView() {
return this;
}
void BubbleDialogDelegate::OnBubbleWidgetClosing() {
if (GetAnchorView() &&
GetAnchorView()->GetProperty(kAnchoredDialogKey) == this)
GetAnchorView()->ClearProperty(kAnchoredDialogKey);
}
void BubbleDialogDelegate::OnAnchorWidgetDestroying() {
SetAnchorView(nullptr);
}
void BubbleDialogDelegate::OnBubbleWidgetActivationChanged(bool active) {
#if BUILDFLAG(IS_MAC)
if (active && !mac_bubble_closer_) {
mac_bubble_closer_ = std::make_unique<ui::BubbleCloser>(
GetWidget()->GetNativeWindow().GetNativeNSWindow(),
base::BindRepeating(&BubbleDialogDelegate::OnDeactivate,
base::Unretained(this)));
}
#endif
if (!active)
OnDeactivate();
}
void BubbleDialogDelegate::OnAnchorWidgetBoundsChanged() {
if (GetBubbleFrameView())
SizeToContents();
}
BubbleBorder::Shadow BubbleDialogDelegate::GetShadow() const {
return shadow_;
}
View* BubbleDialogDelegate::GetAnchorView() const {
if (!anchor_view_observer_)
return nullptr;
return anchor_view_observer_->anchor_view();
}
void BubbleDialogDelegate::SetMainImage(ui::ImageModel main_image) {
if (main_image_.IsEmpty())
DCHECK(!GetBubbleFrameView());
main_image_ = std::move(main_image);
if (GetBubbleFrameView())
GetBubbleFrameView()->UpdateMainImage();
}
bool BubbleDialogDelegate::ShouldCloseOnDeactivate() const {
return close_on_deactivate_ && !close_on_deactivate_pins_->is_pinned();
}
std::unique_ptr<BubbleDialogDelegate::CloseOnDeactivatePin>
BubbleDialogDelegate::PreventCloseOnDeactivate() {
return base::WrapUnique(
new CloseOnDeactivatePin(close_on_deactivate_pins_->GetWeakPtr()));
}
void BubbleDialogDelegate::SetHighlightedButton(Button* highlighted_button) {
bool visible = GetWidget() && GetWidget()->IsVisible();
if (visible && highlighted_button != highlighted_button_tracker_.view())
UpdateHighlightedButton(false);
highlighted_button_tracker_.SetView(highlighted_button);
if (visible)
UpdateHighlightedButton(true);
}
void BubbleDialogDelegate::SetArrow(BubbleBorder::Arrow arrow) {
SetArrowWithoutResizing(arrow);
if (GetBubbleFrameView())
SizeToContents();
}
void BubbleDialogDelegate::SetArrowWithoutResizing(BubbleBorder::Arrow arrow) {
if (base::i18n::IsRTL())
arrow = BubbleBorder::horizontal_mirror(arrow);
if (arrow_ == arrow)
return;
arrow_ = arrow;
if (GetBubbleFrameView())
GetBubbleFrameView()->SetArrow(arrow);
}
gfx::Rect BubbleDialogDelegate::GetAnchorRect() const {
View* anchor_view = GetAnchorView();
if (!anchor_view)
return anchor_rect_.value_or(gfx::Rect());
anchor_rect_ = anchor_view->GetAnchorBoundsInScreen();
#if !BUILDFLAG(IS_MAC)
DCHECK_EQ(anchor_widget_, anchor_view->GetWidget());
if (anchor_widget_) {
gfx::Transform transform =
anchor_widget_->GetNativeWindow()->layer()->GetTargetTransform();
if (!transform.IsIdentity())
anchor_rect_->Offset(
-gfx::ToRoundedVector2d(transform.To2dTranslation()));
}
#endif
BubbleFrameView* frame_view = GetBubbleFrameView();
if (frame_view && frame_view->GetDisplayVisibleArrow()) {
gfx::Insets* padding = anchor_view->GetProperty(kInternalPaddingKey);
if (padding != nullptr)
anchor_rect_->Inset(*padding);
}
return anchor_rect_.value();
}
SkColor BubbleDialogDelegate::GetBackgroundColor() {
UpdateColorsFromTheme();
return color();
}
ui::LayerType BubbleDialogDelegate::GetLayerType() const {
return ui::LAYER_TEXTURED;
}
void BubbleDialogDelegate::SetPaintClientToLayer(bool paint_client_to_layer) {
DCHECK(!client_view_);
paint_client_to_layer_ = paint_client_to_layer;
}
void BubbleDialogDelegate::UseCompactMargins() {
set_margins(gfx::Insets(6));
}
gfx::Size BubbleDialogDelegate::GetMaxAvailableScreenSpaceToPlaceBubble(
View* anchor_view,
BubbleBorder::Arrow arrow,
bool adjust_if_offscreen,
BubbleFrameView::PreferredArrowAdjustment arrow_adjustment) {
DCHECK(arrow == BubbleBorder::TOP_LEFT || arrow == BubbleBorder::TOP_RIGHT ||
arrow == BubbleBorder::BOTTOM_RIGHT ||
arrow == BubbleBorder::BOTTOM_LEFT);
DCHECK_EQ(arrow_adjustment,
BubbleFrameView::PreferredArrowAdjustment::kMirror);
gfx::Rect anchor_rect = anchor_view->GetAnchorBoundsInScreen();
gfx::Rect screen_rect =
display::Screen::GetScreen()
->GetDisplayNearestPoint(anchor_rect.CenterPoint())
.work_area();
gfx::Size max_available_space;
if (adjust_if_offscreen) {
max_available_space = GetAvailableSpaceToPlaceBubble(
BubbleBorder::TOP_LEFT, anchor_rect, screen_rect);
max_available_space.SetToMax(GetAvailableSpaceToPlaceBubble(
BubbleBorder::TOP_RIGHT, anchor_rect, screen_rect));
max_available_space.SetToMax(GetAvailableSpaceToPlaceBubble(
BubbleBorder::BOTTOM_RIGHT, anchor_rect, screen_rect));
max_available_space.SetToMax(GetAvailableSpaceToPlaceBubble(
BubbleBorder::BOTTOM_LEFT, anchor_rect, screen_rect));
} else {
max_available_space =
GetAvailableSpaceToPlaceBubble(arrow, anchor_rect, screen_rect);
}
return max_available_space;
}
gfx::Size BubbleDialogDelegate::GetAvailableSpaceToPlaceBubble(
BubbleBorder::Arrow arrow,
gfx::Rect anchor_rect,
gfx::Rect screen_rect) {
int available_height_below = screen_rect.bottom() - anchor_rect.bottom();
int available_height_above = anchor_rect.y() - screen_rect.y();
int available_width_on_left = anchor_rect.right() - screen_rect.x();
int available_width_on_right = screen_rect.right() - anchor_rect.x();
return {BubbleBorder::is_arrow_on_left(arrow) ? available_width_on_right
: available_width_on_left,
BubbleBorder::is_arrow_on_top(arrow) ? available_height_below
: available_height_above};
}
void BubbleDialogDelegate::OnAnchorBoundsChanged() {
if (!GetWidget())
return;
SizeToContents();
UpdateInputProtectorsTimeStamp();
}
void BubbleDialogDelegate::UpdateInputProtectorsTimeStamp() {
if (auto* dialog = GetDialogClientView())
dialog->UpdateInputProtectorTimeStamp();
GetBubbleFrameView()->UpdateInputProtectorTimeStamp();
}
gfx::Rect BubbleDialogDelegate::GetBubbleBounds() {
bool anchor_minimized = anchor_widget() && anchor_widget()->IsMinimized();
gfx::Rect anchor_rect = GetAnchorRect();
bool has_anchor = GetAnchorView() || anchor_rect != gfx::Rect();
return GetBubbleFrameView()->GetUpdatedWindowBounds(
anchor_rect, arrow(), GetWidget()->client_view()->GetPreferredSize(),
adjust_if_offscreen_ && !anchor_minimized && has_anchor);
}
ax::mojom::Role BubbleDialogDelegate::GetAccessibleWindowRole() {
const ax::mojom::Role accessible_role =
WidgetDelegate::GetAccessibleWindowRole();
if (accessible_role != ax::mojom::Role::kWindow)
return accessible_role;
if (GetInitiallyFocusedView())
return ax::mojom::Role::kDialog;
return ax::mojom::Role::kAlertDialog;
}
gfx::Size BubbleDialogDelegateView::GetMinimumSize() const {
return gfx::Size();
}
gfx::Size BubbleDialogDelegateView::GetMaximumSize() const {
return gfx::Size();
}
void BubbleDialogDelegate::SetAnchorView(View* anchor_view) {
if (anchor_view && anchor_view->GetWidget()) {
anchor_widget_observer_ =
std::make_unique<AnchorWidgetObserver>(this, anchor_view->GetWidget());
} else {
anchor_widget_observer_.reset();
}
if (GetAnchorView()) {
if (GetAnchorView()->GetProperty(kAnchoredDialogKey) == this)
GetAnchorView()->ClearProperty(kAnchoredDialogKey);
anchor_view_observer_.reset();
}
if (!anchor_view || anchor_widget() != anchor_view->GetWidget()) {
if (anchor_widget()) {
if (GetWidget() && GetWidget()->IsVisible())
UpdateHighlightedButton(false);
anchor_widget_ = nullptr;
}
if (anchor_view) {
anchor_widget_ = anchor_view->GetWidget();
if (anchor_widget_) {
const bool visible = GetWidget() && GetWidget()->IsVisible();
UpdateHighlightedButton(visible);
}
}
}
if (anchor_view) {
anchor_view_observer_ =
std::make_unique<AnchorViewObserver>(this, anchor_view);
OnAnchorBoundsChanged();
SetAnchoredDialogKey();
}
}
void BubbleDialogDelegate::SetAnchorRect(const gfx::Rect& rect) {
anchor_rect_ = rect;
if (GetWidget())
OnAnchorBoundsChanged();
}
void BubbleDialogDelegate::SizeToContents() {
gfx::Rect bubble_bounds = GetBubbleBounds();
#if BUILDFLAG(IS_MAC)
gfx::Size actual_size =
GetWindowSizeForClientSize(GetWidget(), bubble_bounds.size());
bubble_bounds.set_size(actual_size);
#endif
GetWidget()->SetBounds(bubble_bounds);
}
std::u16string BubbleDialogDelegate::GetSubtitle() const {
return subtitle_;
}
void BubbleDialogDelegate::SetSubtitle(const std::u16string& subtitle) {
if (subtitle_ == subtitle)
return;
subtitle_ = subtitle;
BubbleFrameView* frame_view = GetBubbleFrameView();
if (frame_view)
frame_view->UpdateSubtitle();
}
void BubbleDialogDelegate::UpdateColorsFromTheme() {
View* const contents_view = GetContentsView();
DCHECK(contents_view);
if (!color_explicitly_set()) {
set_color_internal(contents_view->GetColorProvider()->GetColor(
ui::kColorBubbleBackground));
}
BubbleFrameView* frame_view = GetBubbleFrameView();
if (frame_view)
frame_view->SetBackgroundColor(color());
const bool contents_layer_opaque =
contents_view->layer() && contents_view->layer()->fills_bounds_opaquely();
contents_view->SetBackground(contents_layer_opaque ||
force_create_contents_background_
? CreateSolidBackground(color())
: nullptr);
}
void BubbleDialogDelegate::OnBubbleWidgetVisibilityChanged(bool visible) {
UpdateHighlightedButton(visible);
if (visible && ui::IsAlert(GetAccessibleWindowRole())) {
GetWidget()->GetRootView()->NotifyAccessibilityEvent(
ax::mojom::Event::kAlert, true);
}
}
void BubbleDialogDelegate::OnDeactivate() {
if (ShouldCloseOnDeactivate() && GetWidget())
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
}
void BubbleDialogDelegate::NotifyAnchoredBubbleIsPrimary() {
const bool visible = GetWidget() && GetWidget()->IsVisible();
UpdateHighlightedButton(visible);
SetAnchoredDialogKey();
}
void BubbleDialogDelegate::SetAnchoredDialogKey() {
auto* anchor_view = GetAnchorView();
DCHECK(anchor_view);
if (focus_traversable_from_anchor_view_) {
auto* old_anchored_dialog = anchor_view->GetProperty(kAnchoredDialogKey);
if (old_anchored_dialog && old_anchored_dialog != this)
DLOG(WARNING) << "|anchor_view| has already anchored a focusable widget.";
anchor_view->SetProperty(kAnchoredDialogKey,
static_cast<DialogDelegate*>(this));
}
}
void BubbleDialogDelegate::UpdateHighlightedButton(bool highlighted) {
Button* button = Button::AsButton(highlighted_button_tracker_.view());
button = button ? button : Button::AsButton(GetAnchorView());
if (button && highlight_button_when_shown_) {
if (highlighted) {
button_anchor_higlight_ = button->AddAnchorHighlight();
} else {
button_anchor_higlight_.reset();
}
}
}
BEGIN_METADATA(BubbleDialogDelegateView, View)
END_METADATA
}