blob: 516a9aedd88cc056ab249797dfed536557a61028 [file] [log] [blame]
// Copyright 2021 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 "ash/wm/desks/templates/saved_desk_dialog_controller.h"
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/constants/app_types.h"
#include "ash/public/cpp/desks_templates_delegate.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/wm/desks/templates/saved_desk_grid_view.h"
#include "ash/wm/desks/templates/saved_desk_icon_container.h"
#include "ash/wm/desks/templates/saved_desk_item_view.h"
#include "ash/wm/desks/templates/saved_desk_library_view.h"
#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "base/bind.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/window/dialog_delegate.h"
namespace ash {
namespace {
std::u16string GetStringWithQuotes(const std::u16string& str) {
return u"\"" + str + u"\"";
}
} // namespace
// The client view of the dialog. Contains a label which is a description, and
// optionally a couple images of unsupported apps. This dialog will block the
// entire system.
class SavedDeskDialog : public views::DialogDelegateView {
public:
METADATA_HEADER(SavedDeskDialog);
SavedDeskDialog() {
SetModalType(ui::MODAL_TYPE_SYSTEM);
SetShowCloseButton(false);
SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
l10n_util::GetStringUTF16(IDS_APP_CANCEL));
auto* layout_provider = views::LayoutProvider::Get();
set_fixed_width(layout_provider->GetDistanceMetric(
views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical,
layout_provider->GetInsetsMetric(views::InsetsMetric::INSETS_DIALOG),
layout_provider->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_VERTICAL)));
// Add the description for the dialog.
AddChildView(
views::Builder<views::Label>()
.CopyAddressTo(&description_label_)
.SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 14,
gfx::Font::Weight::NORMAL))
.SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kTextColorPrimary))
.SetMultiLine(true)
.SetHorizontalAlignment(gfx::ALIGN_LEFT)
.Build());
}
SavedDeskDialog(const SavedDeskDialog&) = delete;
SavedDeskDialog& operator=(const SavedDeskDialog&) = delete;
~SavedDeskDialog() override = default;
void SetTitleText(int message_id) {
SetTitle(l10n_util::GetStringUTF16(message_id));
}
void SetConfirmButtonText(int message_id) {
SetButtonLabel(ui::DIALOG_BUTTON_OK, l10n_util::GetStringUTF16(message_id));
}
void SetDescriptionText(const std::u16string& text) {
DCHECK(description_label_);
description_label_->SetText(text);
}
void SetDescriptionAccessibleName(const std::u16string& accessible_name) {
DCHECK(description_label_);
description_label_->SetAccessibleName(accessible_name);
}
private:
views::Label* description_label_ = nullptr;
};
BEGIN_VIEW_BUILDER(/* no export */, SavedDeskDialog, views::DialogDelegateView)
VIEW_BUILDER_PROPERTY(int, TitleText)
VIEW_BUILDER_PROPERTY(int, ConfirmButtonText)
VIEW_BUILDER_PROPERTY(std::u16string, DescriptionText)
VIEW_BUILDER_PROPERTY(std::u16string, DescriptionAccessibleName)
END_VIEW_BUILDER
BEGIN_METADATA(SavedDeskDialog, views::DialogDelegateView)
END_METADATA
} // namespace ash
// Must be in global namespace and defined before usage.
DEFINE_VIEW_BUILDER(/* no export */, ash::SavedDeskDialog)
namespace ash {
//-----------------------------------------------------------------------------
// SavedDeskDialogController:
SavedDeskDialogController::SavedDeskDialogController() = default;
SavedDeskDialogController::~SavedDeskDialogController() {
if (dialog_widget_ && !dialog_widget_->IsClosed())
dialog_widget_->CloseNow();
}
void SavedDeskDialogController::ShowUnsupportedAppsDialog(
aura::Window* root_window,
const std::vector<aura::Window*>& unsupported_apps,
DesksController::GetDeskTemplateCallback callback,
std::unique_ptr<DeskTemplate> desk_template) {
// We can only bind `callback` once but the user has three possible paths
// (accept, cancel, or close) so store `callback` and `desk_template` for
// future usage once we know the user's decision.
unsupported_apps_callback_ = std::move(callback);
unsupported_apps_template_ = std::move(desk_template);
size_t incognito_window_count = 0;
bool contains_lacros_window = false;
auto* delegate = Shell::Get()->desks_templates_delegate();
// TODO(shidi): The caller of ShowUnsupportedAppsDialog should provide us
// with the incognito window count to avoid double looping.
for (auto* window : unsupported_apps) {
if (static_cast<AppType>(window->GetProperty(aura::client::kAppType)) ==
AppType::LACROS) {
contains_lacros_window = true;
break;
}
if (delegate->IsIncognitoWindow(window))
++incognito_window_count;
}
// Note that this assumed unsupported apps which are not incognito browsers
// are linux apps.
std::u16string app_description;
int app_description_id;
if (contains_lacros_window) {
app_description_id =
IDS_ASH_DESKS_TEMPLATES_UNSUPPORTED_LACROS_DIALOG_DESCRIPTION;
} else if (incognito_window_count == 0) {
app_description_id =
IDS_ASH_DESKS_TEMPLATES_UNSUPPORTED_LINUX_APPS_DIALOG_DESCRIPTION;
} else if (incognito_window_count != unsupported_apps.size()) {
app_description_id =
IDS_ASH_DESKS_TEMPLATES_UNSUPPORTED_LINUX_APPS_AND_INCOGNITO_DIALOG_DESCRIPTION;
} else {
app_description_id =
IDS_ASH_DESKS_TEMPLATES_UNSUPPORTED_INCOGNITO_DIALOG_DESCRIPTION;
}
auto dialog =
views::Builder<SavedDeskDialog>()
.SetTitleText(
unsupported_apps_template_->type() == DeskTemplateType::kTemplate
? IDS_ASH_DESKS_TEMPLATES_UNSUPPORTED_APPS_IN_TEMPLATE_DIALOG_TITLE
: IDS_ASH_DESKS_TEMPLATES_UNSUPPORTED_APPS_IN_DESK_DIALOG_TITLE)
.SetConfirmButtonText(
IDS_ASH_DESKS_TEMPLATES_UNSUPPORTED_APPS_DIALOG_CONFIRM_BUTTON)
.SetDescriptionText(l10n_util::GetStringUTF16(app_description_id))
.SetCancelCallback(base::BindOnce(
&SavedDeskDialogController::OnUserCanceledUnsupportedAppsDialog,
weak_ptr_factory_.GetWeakPtr()))
.SetCloseCallback(base::BindOnce(
&SavedDeskDialogController::OnUserCanceledUnsupportedAppsDialog,
weak_ptr_factory_.GetWeakPtr()))
.SetAcceptCallback(base::BindOnce(
&SavedDeskDialogController::OnUserAcceptedUnsupportedAppsDialog,
weak_ptr_factory_.GetWeakPtr()))
.AddChildren(
views::Builder<views::Label>()
.SetHorizontalAlignment(gfx::ALIGN_LEFT)
.SetFontList(gfx::FontList({"Roboto"}, gfx::Font::NORMAL, 14,
gfx::Font::Weight::MEDIUM))
.SetEnabledColor(
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::
kTextColorPrimary))
.SetText(l10n_util::GetStringUTF16(
IDS_ASH_DESKS_TEMPLATES_UNSUPPORTED_APPS_DIALOG_HEADER)),
views::Builder<SavedDeskIconContainer>()
.PopulateIconContainerFromWindows(unsupported_apps))
.Build();
CreateDialogWidget(std::move(dialog), root_window);
RecordUnsupportedAppDialogShowHistogram(unsupported_apps_template_->type());
}
void SavedDeskDialogController::ShowReplaceDialog(
aura::Window* root_window,
const std::u16string& template_name,
DeskTemplateType template_type,
base::OnceClosure on_accept_callback,
base::OnceClosure on_cancel_callback) {
if (!CanShowDialog())
return;
auto dialog =
views::Builder<SavedDeskDialog>()
.SetTitleText(
template_type == DeskTemplateType::kTemplate
? IDS_ASH_DESKS_TEMPLATES_REPLACE_TEMPLATE_DIALOG_TITLE
: IDS_ASH_DESKS_TEMPLATES_REPLACE_DESK_DIALOG_TITLE)
.SetConfirmButtonText(
IDS_ASH_DESKS_TEMPLATES_REPLACE_DIALOG_CONFIRM_BUTTON)
.SetDescriptionText(l10n_util::GetStringFUTF16(
template_type == DeskTemplateType::kTemplate
? IDS_ASH_DESKS_TEMPLATES_REPLACE_TEMPLATE_DIALOG_DESCRIPTION
: IDS_ASH_DESKS_TEMPLATES_REPLACE_DESK_DIALOG_DESCRIPTION,
GetStringWithQuotes(template_name)))
.SetDescriptionAccessibleName(l10n_util::GetStringFUTF16(
template_type == DeskTemplateType::kTemplate
? IDS_ASH_DESKS_TEMPLATES_REPLACE_TEMPLATE_DIALOG_DESCRIPTION
: IDS_ASH_DESKS_TEMPLATES_REPLACE_DESK_DIALOG_DESCRIPTION,
template_name))
.SetAcceptCallback(std::move(on_accept_callback))
.SetCancelCallback(std::move(on_cancel_callback))
.Build();
CreateDialogWidget(std::move(dialog), root_window);
}
void SavedDeskDialogController::ShowDeleteDialog(
aura::Window* root_window,
const std::u16string& template_name,
DeskTemplateType template_type,
base::OnceClosure on_accept_callback) {
if (!CanShowDialog())
return;
auto dialog =
views::Builder<SavedDeskDialog>()
.SetTitleText(
template_type == DeskTemplateType::kTemplate
? IDS_ASH_DESKS_TEMPLATES_DELETE_TEMPLATE_DIALOG_TITLE
: IDS_ASH_DESKS_TEMPLATES_DELETE_DESK_DIALOG_TITLE)
.SetButtonLabel(
ui::DIALOG_BUTTON_OK,
l10n_util::GetStringUTF16(
IDS_ASH_DESKS_TEMPLATES_DELETE_DIALOG_CONFIRM_BUTTON))
.SetDescriptionText(l10n_util::GetStringFUTF16(
IDS_ASH_DESKS_TEMPLATES_DELETE_DIALOG_DESCRIPTION,
GetStringWithQuotes(template_name)))
.SetDescriptionAccessibleName(l10n_util::GetStringFUTF16(
IDS_ASH_DESKS_TEMPLATES_DELETE_DIALOG_DESCRIPTION, template_name))
.SetAcceptCallback(std::move(on_accept_callback))
.Build();
CreateDialogWidget(std::move(dialog), root_window);
}
void SavedDeskDialogController::OnWidgetDestroying(views::Widget* widget) {
DCHECK_EQ(dialog_widget_, widget);
for (auto& overview_grid :
Shell::Get()->overview_controller()->overview_session()->grid_list()) {
if (auto* library_view = overview_grid->GetSavedDeskLibraryView()) {
for (auto* templates_grid_view : library_view->grid_views()) {
for (SavedDeskItemView* saved_desk_item :
templates_grid_view->grid_items()) {
// Update the button visibility when a dialog is closed.
saved_desk_item->UpdateHoverButtonsVisibility(
aura::Env::GetInstance()->last_mouse_location(),
/*is_touch=*/false);
}
}
}
}
dialog_widget_observation_.Reset();
dialog_widget_ = nullptr;
}
void SavedDeskDialogController::CreateDialogWidget(
std::unique_ptr<SavedDeskDialog> dialog,
aura::Window* root_window) {
// We should not get here with an active dialog.
DCHECK_EQ(dialog_widget_, nullptr);
// The dialog will show on the display associated with `root_window`, and will
// block all input since it is system modal.
DCHECK(root_window->IsRootWindow());
dialog_widget_ = views::DialogDelegate::CreateDialogWidget(
std::move(dialog),
/*context=*/root_window, /*parent=*/nullptr);
dialog_widget_->GetNativeWindow()->SetName("TemplateDialogForTesting");
dialog_widget_->Show();
dialog_widget_observation_.Observe(dialog_widget_);
// Ensure that if ChromeVox is enabled, it focuses on the dialog.
AccessibilityControllerImpl* accessibility_controller =
Shell::Get()->accessibility_controller();
if (accessibility_controller->spoken_feedback().enabled()) {
accessibility_controller->SetA11yOverrideWindow(
dialog_widget_->GetNativeWindow());
}
}
bool SavedDeskDialogController::CanShowDialog() const {
// Cannot show a dialog while another is active.
return dialog_widget_ == nullptr;
}
void SavedDeskDialogController::OnUserAcceptedUnsupportedAppsDialog() {
DCHECK(!unsupported_apps_callback_.is_null());
DCHECK(unsupported_apps_template_);
std::move(unsupported_apps_callback_)
.Run(std::move(unsupported_apps_template_));
}
void SavedDeskDialogController::OnUserCanceledUnsupportedAppsDialog() {
DCHECK(!unsupported_apps_callback_.is_null());
std::move(unsupported_apps_callback_).Run(nullptr);
unsupported_apps_template_.reset();
}
} // namespace ash