| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/widget/widget_delegate.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/accessibility/ax_enums.mojom.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/base/models/image_model.h" |
| #include "ui/base/mojom/window_show_state.mojom.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/views/view.h" |
| #include "ui/views/views_delegate.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/window/client_view.h" |
| |
| namespace views { |
| |
| namespace { |
| |
| std::unique_ptr<ClientView> CreateDefaultClientView( |
| Widget* widget, |
| views::View* contents_view) { |
| return std::make_unique<ClientView>(widget, contents_view); |
| } |
| |
| std::unique_ptr<FrameView> CreateDefaultFrameView(Widget* widget) { |
| return nullptr; |
| } |
| |
| std::unique_ptr<View> CreateDefaultOverlayView() { |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // WidgetDelegate: |
| |
| WidgetDelegate::Params::Params() = default; |
| WidgetDelegate::Params::~Params() = default; |
| |
| WidgetDelegate::WidgetDelegate() |
| : widget_initialized_callbacks_(std::make_unique<ClosureVector>()), |
| client_view_factory_(base::BindOnce(&CreateDefaultClientView)), |
| frame_view_factory_(base::BindRepeating(&CreateDefaultFrameView)), |
| overlay_view_factory_(base::BindOnce(&CreateDefaultOverlayView)) {} |
| |
| WidgetDelegate::~WidgetDelegate() { |
| CHECK(can_delete_this_) << "A WidgetDelegate must outlive its Widget"; |
| if (!contents_view_taken_ && default_contents_view_ && |
| !default_contents_view_->parent()) { |
| delete default_contents_view_; |
| default_contents_view_ = nullptr; |
| } |
| } |
| |
| void WidgetDelegate::SetCanActivate(bool can_activate) { |
| can_activate_ = can_activate; |
| } |
| |
| void WidgetDelegate::OnWidgetMove() {} |
| |
| void WidgetDelegate::OnDisplayChanged() {} |
| |
| void WidgetDelegate::OnWorkAreaChanged() {} |
| |
| bool WidgetDelegate::OnCloseRequested(Widget::ClosedReason close_reason) { |
| return true; |
| } |
| |
| View* WidgetDelegate::GetInitiallyFocusedView() { |
| return params_.initially_focused_view.value_or(nullptr); |
| } |
| |
| bool WidgetDelegate::HasConfiguredInitiallyFocusedView() const { |
| return params_.initially_focused_view.has_value(); |
| } |
| |
| BubbleDialogDelegate* WidgetDelegate::AsBubbleDialogDelegate() { |
| return nullptr; |
| } |
| |
| DialogDelegate* WidgetDelegate::AsDialogDelegate() { |
| return nullptr; |
| } |
| |
| bool WidgetDelegate::CanResize() const { |
| return params_.can_resize; |
| } |
| |
| bool WidgetDelegate::CanMaximize() const { |
| return params_.can_maximize; |
| } |
| |
| bool WidgetDelegate::CanMinimize() const { |
| return params_.can_minimize; |
| } |
| |
| bool WidgetDelegate::CanFullscreen() const { |
| return params_.can_fullscreen; |
| } |
| |
| bool WidgetDelegate::CanActivate() const { |
| return can_activate_; |
| } |
| |
| ui::mojom::ModalType WidgetDelegate::GetModalType() const { |
| return params_.modal_type; |
| } |
| |
| ax::mojom::Role WidgetDelegate::GetAccessibleWindowRole() { |
| return params_.accessible_role; |
| } |
| |
| std::u16string WidgetDelegate::GetAccessibleWindowTitle() const { |
| return params_.accessible_title.empty() ? GetWindowTitle() |
| : params_.accessible_title; |
| } |
| |
| std::u16string WidgetDelegate::GetWindowTitle() const { |
| return params_.title; |
| } |
| |
| bool WidgetDelegate::ShouldShowWindowTitle() const { |
| return params_.show_title; |
| } |
| |
| bool WidgetDelegate::ShouldCenterWindowTitleText() const { |
| #if defined(USE_AURA) |
| return params_.center_title; |
| #else |
| return false; |
| #endif |
| } |
| |
| // TODO(ffred): refactor this method. |
| bool WidgetDelegate::RotatePaneFocusFromView(View* focused_view, |
| bool forward, |
| bool enable_wrapping) { |
| // Get the list of all accessible panes. |
| std::vector<View*> panes; |
| GetAccessiblePanes(&panes); |
| |
| // Count the number of panes and set the default index if no pane |
| // is initially focused. |
| const size_t count = panes.size(); |
| if (!count) { |
| return false; |
| } |
| |
| // Initialize |index| to an appropriate starting index if nothing is |
| // focused initially. |
| size_t index = forward ? (count - 1) : 0; |
| |
| // Check to see if a pane already has focus and update the index accordingly. |
| if (focused_view) { |
| const auto i = |
| std::ranges::find_if(panes, [focused_view](const auto* pane) { |
| return pane && pane->Contains(focused_view); |
| }); |
| if (i != panes.cend()) { |
| index = static_cast<size_t>(i - panes.cbegin()); |
| } |
| } |
| |
| // Rotate focus. |
| for (const size_t start_index = index;;) { |
| index = (!forward ? (index + count - 1) : (index + 1)) % count; |
| |
| if (!enable_wrapping && (index == (forward ? 0 : (count - 1)))) { |
| return false; |
| } |
| |
| // Ensure that we don't loop more than once. |
| if (index == start_index) { |
| return false; |
| } |
| |
| views::View* pane = panes[index]; |
| DCHECK(pane); |
| if (pane->GetVisible()) { |
| pane->RequestFocus(); |
| // |pane| may be in a different widget, so don't assume its focus manager |
| // is |this|. |
| focused_view = pane->GetWidget()->GetFocusManager()->GetFocusedView(); |
| if (pane == focused_view || pane->Contains(focused_view)) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| void WidgetDelegate::SetTitleChangedCallback(TitleChangedCallback callback) { |
| title_changed_callback_ = std::move(callback); |
| } |
| |
| void WidgetDelegate::SetAccessibleTitleChangedCallback( |
| AccessibleTitleChangedCallback callback) { |
| accessible_title_changed_callback_ = std::move(callback); |
| } |
| |
| bool WidgetDelegate::ShouldShowCloseButton() const { |
| return params_.show_close_button; |
| } |
| |
| ui::ImageModel WidgetDelegate::GetWindowAppIcon() { |
| // Prefer app icon if available. |
| if (!params_.app_icon.IsEmpty()) { |
| return params_.app_icon; |
| } |
| // Fall back to the window icon. |
| return GetWindowIcon(); |
| } |
| |
| // Returns the icon to be displayed in the window. |
| ui::ImageModel WidgetDelegate::GetWindowIcon() { |
| return params_.icon; |
| } |
| |
| bool WidgetDelegate::ShouldShowWindowIcon() const { |
| return params_.show_icon; |
| } |
| |
| bool WidgetDelegate::ExecuteWindowsCommand(int command_id) { |
| return false; |
| } |
| |
| std::string WidgetDelegate::GetWindowName() const { |
| return std::string(); |
| } |
| |
| void WidgetDelegate::SaveWindowPlacement( |
| const gfx::Rect& bounds, |
| ui::mojom::WindowShowState show_state) { |
| std::string window_name = GetWindowName(); |
| if (!window_name.empty()) { |
| ViewsDelegate::GetInstance()->SaveWindowPlacement(GetWidget(), window_name, |
| bounds, show_state); |
| } |
| } |
| |
| bool WidgetDelegate::ShouldSaveWindowPlacement() const { |
| return !GetWindowName().empty(); |
| } |
| |
| bool WidgetDelegate::GetSavedWindowPlacement( |
| const Widget* widget, |
| gfx::Rect* bounds, |
| ui::mojom::WindowShowState* show_state) const { |
| std::string window_name = GetWindowName(); |
| if (window_name.empty() || |
| !ViewsDelegate::GetInstance()->GetSavedWindowPlacement( |
| widget, window_name, bounds, show_state)) { |
| return false; |
| } |
| // Try to find a display intersecting the saved bounds. |
| const auto& display = display::Screen::Get()->GetDisplayMatching(*bounds); |
| return display.bounds().Intersects(*bounds); |
| } |
| |
| base::WeakPtr<WidgetDelegate> WidgetDelegate::AttachWidgetAndGetHandle( |
| Widget* widget) { |
| can_delete_this_ = false; |
| |
| widget_ = widget; |
| // This weak ptr is valid until `DeleteDelegate` is called. This |
| // will be called otherwise the dtor will CHECK on `can_delete_this_`. |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void WidgetDelegate::WidgetInitialized() { |
| for (auto&& callback : *widget_initialized_callbacks_) { |
| std::move(callback).Run(); |
| } |
| widget_initialized_callbacks_.reset(); |
| OnWidgetInitialized(); |
| } |
| |
| void WidgetDelegate::WidgetDestroying() { |
| widget_ = nullptr; |
| } |
| |
| void WidgetDelegate::WindowWillClose() { |
| // TODO(ellyjones): For this and the other callback methods, establish whether |
| // any other code calls these methods. If not, DCHECK here and below that |
| // these methods are only called once. |
| for (auto&& callback : window_will_close_callbacks_) { |
| std::move(callback).Run(); |
| } |
| } |
| |
| void WidgetDelegate::WindowClosing() { |
| for (auto&& callback : window_closing_callbacks_) { |
| std::move(callback).Run(); |
| } |
| } |
| |
| void WidgetDelegate::DeleteDelegate() { |
| can_delete_this_ = true; |
| |
| bool owned_by_widget = owned_by_widget_; |
| ClosureVector delete_callbacks; |
| delete_callbacks.swap(delete_delegate_callbacks_); |
| |
| const auto weak_this = weak_ptr_factory_.GetWeakPtr(); |
| for (auto&& callback : delete_callbacks) { |
| std::move(callback).Run(); |
| } |
| |
| if (weak_this && !owned_by_widget && widget_ && |
| widget_->ownership() == Widget::InitParams::CLIENT_OWNS_WIDGET) { |
| WidgetIsZombie(widget_.get()); |
| } |
| |
| // TODO(kylixrd): Eventually the widget will never own the delegate, so much |
| // of this code will need to be reworked. |
| // |
| // If the WidgetDelegate is owned by the Widget, it is illegal for the |
| // DeleteDelegate callbacks to destruct it; if it is not owned by the Widget, |
| // the DeleteDelete callbacks are allowed but not required to destroy it. |
| if (owned_by_widget) { |
| DCHECK(weak_this); |
| // TODO(kylxird): Rework this once the Widget stops being able to "own" the |
| // delegate. |
| delete this; |
| } else if (weak_this) { |
| widget_ = nullptr; |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| } |
| } |
| |
| Widget* WidgetDelegate::GetWidget() { |
| return widget_; |
| } |
| |
| const Widget* WidgetDelegate::GetWidget() const { |
| return widget_; |
| } |
| |
| View* WidgetDelegate::GetContentsView() { |
| if (unowned_contents_view_) { |
| return unowned_contents_view_; |
| } |
| if (!default_contents_view_) { |
| default_contents_view_ = new View; |
| } |
| return default_contents_view_; |
| } |
| |
| View* WidgetDelegate::TransferOwnershipOfContentsView() { |
| DCHECK(!contents_view_taken_); |
| contents_view_taken_ = true; |
| if (owned_contents_view_) { |
| owned_contents_view_.release(); |
| } |
| return GetContentsView(); |
| } |
| |
| ClientView* WidgetDelegate::CreateClientView(Widget* widget) { |
| DCHECK(client_view_factory_); |
| return std::move(client_view_factory_) |
| .Run(widget, TransferOwnershipOfContentsView()) |
| .release(); |
| } |
| |
| std::unique_ptr<FrameView> WidgetDelegate::CreateFrameView(Widget* widget) { |
| CHECK(frame_view_factory_); |
| return frame_view_factory_.Run(widget); |
| } |
| |
| View* WidgetDelegate::CreateOverlayView() { |
| DCHECK(overlay_view_factory_); |
| return std::move(overlay_view_factory_).Run().release(); |
| } |
| |
| bool WidgetDelegate::WidgetHasHitTestMask() const { |
| return false; |
| } |
| |
| void WidgetDelegate::GetWidgetHitTestMask(SkPath* mask) const { |
| DCHECK(mask); |
| } |
| |
| bool WidgetDelegate::ShouldDescendIntoChildForEventHandling( |
| gfx::NativeView child, |
| const gfx::Point& location) { |
| return true; |
| } |
| |
| void WidgetDelegate::SetAccessibleWindowRole(ax::mojom::Role role) { |
| params_.accessible_role = role; |
| } |
| |
| void WidgetDelegate::SetAccessibleTitle(std::u16string title) { |
| params_.accessible_title = std::move(title); |
| |
| if (accessible_title_changed_callback_) { |
| accessible_title_changed_callback_.Run(); |
| } |
| } |
| |
| void WidgetDelegate::SetCanFullscreen(bool can_fullscreen) { |
| bool old_can_fullscreen = |
| std::exchange(params_.can_fullscreen, can_fullscreen); |
| if (GetWidget() && params_.can_fullscreen != old_can_fullscreen) { |
| GetWidget()->OnSizeConstraintsChanged(); |
| } |
| } |
| |
| void WidgetDelegate::SetCanMaximize(bool can_maximize) { |
| bool old_can_maximize = std::exchange(params_.can_maximize, can_maximize); |
| if (GetWidget() && params_.can_maximize != old_can_maximize) { |
| GetWidget()->OnSizeConstraintsChanged(); |
| } |
| } |
| |
| void WidgetDelegate::SetCanMinimize(bool can_minimize) { |
| bool old_can_minimize = std::exchange(params_.can_minimize, can_minimize); |
| if (GetWidget() && params_.can_minimize != old_can_minimize) { |
| GetWidget()->OnSizeConstraintsChanged(); |
| } |
| } |
| |
| void WidgetDelegate::SetCanResize(bool can_resize) { |
| bool old_can_resize = std::exchange(params_.can_resize, can_resize); |
| if (GetWidget() && params_.can_resize != old_can_resize) { |
| GetWidget()->OnSizeConstraintsChanged(); |
| } |
| } |
| |
| void WidgetDelegate::SetOwnedByWidget(OwnedByWidgetPassKey) { |
| owned_by_widget_ = true; |
| } |
| |
| void WidgetDelegate::SetFocusTraversesOut(bool focus_traverses_out) { |
| params_.focus_traverses_out = focus_traverses_out; |
| } |
| |
| void WidgetDelegate::SetEnableArrowKeyTraversal( |
| bool enable_arrow_key_traversal) { |
| params_.enable_arrow_key_traversal = enable_arrow_key_traversal; |
| } |
| |
| void WidgetDelegate::SetIcon(ui::ImageModel icon) { |
| params_.icon = std::move(icon); |
| if (GetWidget()) { |
| GetWidget()->UpdateWindowIcon(); |
| } |
| } |
| |
| void WidgetDelegate::SetAppIcon(ui::ImageModel icon) { |
| params_.app_icon = std::move(icon); |
| if (GetWidget()) { |
| GetWidget()->UpdateWindowIcon(); |
| } |
| } |
| |
| void WidgetDelegate::SetInitiallyFocusedView(View* initially_focused_view) { |
| DCHECK(!GetWidget()); |
| params_.initially_focused_view = initially_focused_view; |
| } |
| |
| void WidgetDelegate::SetModalType(ui::mojom::ModalType modal_type) { |
| DCHECK(!GetWidget()); |
| params_.modal_type = modal_type; |
| } |
| |
| void WidgetDelegate::SetShowCloseButton(bool show_close_button) { |
| params_.show_close_button = show_close_button; |
| } |
| |
| void WidgetDelegate::SetShowIcon(bool show_icon) { |
| params_.show_icon = show_icon; |
| if (GetWidget()) { |
| GetWidget()->UpdateWindowIcon(); |
| } |
| } |
| |
| void WidgetDelegate::SetShowTitle(bool show_title) { |
| params_.show_title = show_title; |
| } |
| |
| void WidgetDelegate::SetTitle(const std::u16string& title) { |
| if (params_.title == title) { |
| return; |
| } |
| params_.title = title; |
| if (GetWidget()) { |
| GetWidget()->UpdateWindowTitle(); |
| } |
| |
| if (title_changed_callback_) { |
| title_changed_callback_.Run(); |
| } |
| } |
| |
| void WidgetDelegate::SetTitle(int title_message_id) { |
| SetTitle(l10n_util::GetStringUTF16(title_message_id)); |
| } |
| |
| #if defined(USE_AURA) |
| void WidgetDelegate::SetCenterTitle(bool center_title) { |
| params_.center_title = center_title; |
| } |
| #endif |
| |
| void WidgetDelegate::SetHasWindowSizeControls(bool has_controls) { |
| SetCanFullscreen(has_controls); |
| SetCanMaximize(has_controls); |
| SetCanMinimize(has_controls); |
| SetCanResize(has_controls); |
| } |
| |
| void WidgetDelegate::RegisterWidgetInitializedCallback( |
| base::OnceClosure callback) { |
| DCHECK(widget_initialized_callbacks_); |
| widget_initialized_callbacks_->emplace_back(std::move(callback)); |
| } |
| |
| void WidgetDelegate::RegisterWindowClosingCallback(base::OnceClosure callback) { |
| window_closing_callbacks_.emplace_back(std::move(callback)); |
| } |
| |
| void WidgetDelegate::RegisterWindowWillCloseCallback( |
| RegisterWillCloseCallbackPassKey, |
| base::OnceClosure callback) { |
| window_will_close_callbacks_.emplace_back(std::move(callback)); |
| } |
| |
| void WidgetDelegate::RegisterDeleteDelegateCallback( |
| RegisterDeleteCallbackPassKey, |
| base::OnceClosure callback) { |
| delete_delegate_callbacks_.emplace_back(std::move(callback)); |
| } |
| |
| void WidgetDelegate::SetClientViewFactory(ClientViewFactory factory) { |
| DCHECK(!GetWidget()); |
| client_view_factory_ = std::move(factory); |
| } |
| |
| void WidgetDelegate::SetFrameViewFactory(FrameViewFactory factory) { |
| DCHECK(!GetWidget()); |
| frame_view_factory_ = std::move(factory); |
| } |
| |
| void WidgetDelegate::SetOverlayViewFactory(OverlayViewFactory factory) { |
| DCHECK(!GetWidget()); |
| overlay_view_factory_ = std::move(factory); |
| } |
| |
| void WidgetDelegate::SetContentsViewImpl(std::unique_ptr<View> contents) { |
| DCHECK(!contents->owned_by_client()); |
| DCHECK(!unowned_contents_view_); |
| owned_contents_view_ = std::move(contents); |
| unowned_contents_view_ = owned_contents_view_.get(); |
| } |
| |
| gfx::Rect WidgetDelegate::GetDesiredWidgetBounds() { |
| DCHECK(GetWidget()); |
| |
| if (has_desired_bounds_delegate()) { |
| const gfx::Rect desired_bounds = params_.desired_bounds_delegate.Run(); |
| // This can for instance be empty during browser shutdown where the delegate |
| // fails to find the appropriate Widget native view to generate bounds. See |
| // GetModalDialogBounds in constrained_window which as of this commit return |
| // empty bounds if it can't find the the host widget. Ideally this Widget |
| // would go away or at least be "in shutdown" and probably avoid this code |
| // path before the underlying host Widget or native view goes away. |
| if (!desired_bounds.IsEmpty()) { |
| return desired_bounds; |
| } |
| } |
| |
| return gfx::Rect(GetWidget()->GetWindowBoundsInScreen().origin(), |
| GetWidget()->GetContentsView()->GetPreferredSize({})); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // WidgetDelegateView: |
| |
| WidgetDelegateView::WidgetDelegateView() = default; |
| |
| WidgetDelegateView::~WidgetDelegateView() = default; |
| |
| Widget* WidgetDelegateView::GetWidget() { |
| return View::GetWidget(); |
| } |
| |
| const Widget* WidgetDelegateView::GetWidget() const { |
| return View::GetWidget(); |
| } |
| |
| views::View* WidgetDelegateView::GetContentsView() { |
| return this; |
| } |
| |
| BEGIN_METADATA(WidgetDelegateView) |
| END_METADATA |
| |
| } // namespace views |