#include "ui/views/controls/message_box_view.h"
#include <stddef.h>
#include <memory>
#include <numeric>
#include <string_view>
#include <utility>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/accessibility/accessibility_paint_checks.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/client_view.h"
#include "ui/views/window/dialog_delegate.h"
namespace {
constexpr int kDefaultMessageWidth = 400;
bool IsParagraphSeparator(char16_t c) {
return (c == 0x000A || c == 0x000D || c == 0x001C || c == 0x001D ||
c == 0x001E || c == 0x0085 || c == 0x2029);
}
void SplitStringIntoParagraphs(std::u16string_view text,
std::vector<std::u16string_view>* paragraphs) {
paragraphs->clear();
size_t start = 0;
for (size_t i = 0; i < text.length(); ++i) {
if (IsParagraphSeparator(text[i])) {
paragraphs->push_back(text.substr(start, i - start));
start = i + 1;
}
}
paragraphs->push_back(text.substr(start, text.length() - start));
}
std::unique_ptr<views::Label> GetLabelView(
std::u16string_view text,
gfx::HorizontalAlignment alignment,
views::MessageBoxView::MessageLabels& message_labels) {
return views::Builder<views::Label>()
.SetTextContext(views::style::CONTEXT_DIALOG_BODY_TEXT)
.SetText(std::u16string(text))
.SetMultiLine(!text.empty())
.SetAllowCharacterBreak(true)
.SetHorizontalAlignment(alignment)
.CustomConfigure(base::BindOnce(
[](views::MessageBoxView::MessageLabels& message_labels,
views::Label* message_label) {
message_labels.push_back(message_label);
},
std::ref(message_labels)))
.Build();
}
}
namespace views {
MessageBoxView::MessageBoxView(const std::u16string& message,
bool detect_directionality)
: detect_directionality_(detect_directionality),
inter_row_vertical_spacing_(LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_RELATED_CONTROL_VERTICAL)),
message_width_(kDefaultMessageWidth) {
const LayoutProvider* provider = LayoutProvider::Get();
auto horizontal_insets = GetHorizontalInsets(provider);
auto message_contents =
Builder<BoxLayoutView>()
.SetOrientation(BoxLayout::Orientation::kVertical)
.SetBorder(CreateEmptyBorder(GetHorizontalInsets(provider)))
.CopyAddressTo(&message_contents_);
Builder<MessageBoxView>(this)
.SetOrientation(BoxLayout::Orientation::kVertical)
.AddChildren(
Builder<ScrollView>()
.CopyAddressTo(&scroll_view_)
.ClipHeightTo(0, provider->GetDistanceMetric(
DISTANCE_DIALOG_SCROLLABLE_AREA_MAX_HEIGHT))
.SetContents(std::move(message_contents)),
Builder<Textfield>()
.CopyAddressTo(&prompt_field_)
.SetProperty(kSkipAccessibilityPaintChecks, true)
.SetProperty(kMarginsKey, horizontal_insets)
.SetAccessibleName(message)
.SetVisible(false)
.CustomConfigure(base::BindOnce([](Textfield* prompt_field) {
prompt_field->GetViewAccessibility().SetIsIgnored(true);
})),
Builder<Checkbox>()
.CopyAddressTo(&checkbox_)
.SetProperty(kMarginsKey, horizontal_insets)
.SetVisible(false),
Builder<Link>()
.CopyAddressTo(&link_)
.SetProperty(kMarginsKey, horizontal_insets)
.SetHorizontalAlignment(gfx::ALIGN_LEFT)
.SetVisible(false))
.BuildChildren();
SetMessageLabel(message);
if (message_labels_.size() == 1u) {
message_labels_[0]->SetSelectable(true);
}
ResetLayoutManager();
}
MessageBoxView::~MessageBoxView() = default;
void MessageBoxView::SetMessageLabel(std::u16string_view message) {
CHECK(message_contents_);
message_labels_.clear();
message_contents_->RemoveAllChildViews();
label_text_ = message;
if (detect_directionality_) {
std::vector<std::u16string_view> texts;
SplitStringIntoParagraphs(message, &texts);
for (const auto& text : texts) {
message_contents_->AddChildView(GetLabelView(
text,
gfx::ALIGN_TO_HEAD,
message_labels_));
}
} else {
message_contents_->AddChildView(GetLabelView(
message,
gfx::ALIGN_LEFT,
message_labels_));
}
}
views::Textfield* MessageBoxView::GetVisiblePromptField() {
return prompt_field_ && prompt_field_->GetVisible() ? prompt_field_.get()
: nullptr;
}
std::u16string_view MessageBoxView::GetInputText() {
return prompt_field_ && prompt_field_->GetVisible() ? prompt_field_->GetText()
: std::u16string_view();
}
bool MessageBoxView::HasVisibleCheckBox() const {
return checkbox_ && checkbox_->GetVisible();
}
bool MessageBoxView::IsCheckBoxSelected() {
return checkbox_ && checkbox_->GetVisible() && checkbox_->GetChecked();
}
void MessageBoxView::SetCheckBoxLabel(const std::u16string& label) {
DCHECK(checkbox_);
if (checkbox_->GetVisible() && checkbox_->GetText() == label) {
return;
}
checkbox_->SetText(label);
checkbox_->SetVisible(true);
ResetLayoutManager();
}
void MessageBoxView::SetCheckBoxSelected(bool selected) {
if (!checkbox_->GetVisible()) {
return;
}
checkbox_->SetChecked(selected);
}
void MessageBoxView::SetLink(const std::u16string& text,
Link::ClickedCallback callback) {
DCHECK(!text.empty());
DCHECK(!callback.is_null());
DCHECK(link_);
link_->SetCallback(std::move(callback));
if (link_->GetVisible() && link_->GetText() == text) {
return;
}
link_->SetText(text);
link_->SetVisible(true);
ResetLayoutManager();
}
void MessageBoxView::SetInterRowVerticalSpacing(int spacing) {
if (inter_row_vertical_spacing_ == spacing) {
return;
}
inter_row_vertical_spacing_ = spacing;
ResetLayoutManager();
}
void MessageBoxView::SetMessageWidth(int width) {
if (message_width_ == width) {
return;
}
message_width_ = width;
ResetLayoutManager();
}
void MessageBoxView::SetPromptField(const std::u16string& default_prompt) {
DCHECK(prompt_field_);
if (prompt_field_->GetVisible() &&
prompt_field_->GetText() == default_prompt) {
return;
}
prompt_field_->SetText(default_prompt);
prompt_field_->SetVisible(true);
prompt_field_->GetViewAccessibility().SetIsIgnored(false);
scroll_view_->GetViewAccessibility().SetIsLeaf(true);
ResetLayoutManager();
}
gfx::Size MessageBoxView::CalculatePreferredSize(
const SizeBounds& available_size) const {
return BoxLayoutView::CalculatePreferredSize(
SizeBounds(message_width_, available_size.height()));
}
void MessageBoxView::ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) {
if (details.child == this && details.is_add) {
if (prompt_field_ && prompt_field_->GetVisible()) {
prompt_field_->SelectAll(true);
}
}
}
bool MessageBoxView::AcceleratorPressed(const ui::Accelerator& accelerator) {
DCHECK_EQ(accelerator.key_code(), 'C');
DCHECK(accelerator.IsCtrlDown());
if (prompt_field_ && prompt_field_->HasFocus()) {
return false;
}
if (message_labels_.size() == 1u && message_labels_[0]->GetSelectable()) {
return false;
}
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteText(std::accumulate(message_labels_.cbegin(),
message_labels_.cend(), std::u16string(),
[](const std::u16string& left, Label* right) {
return base::StrCat({left, right->GetText()});
}));
return true;
}
void MessageBoxView::ResetLayoutManager() {
SetBetweenChildSpacing(inter_row_vertical_spacing_);
SetMinimumCrossAxisSize(message_width_);
views::DialogContentType trailing_content_type =
views::DialogContentType::kText;
if (prompt_field_->GetVisible()) {
trailing_content_type = views::DialogContentType::kControl;
}
bool checkbox_is_visible = checkbox_->GetVisible();
if (checkbox_is_visible) {
trailing_content_type = views::DialogContentType::kText;
}
checkbox_->GetViewAccessibility().SetIsIgnored(!checkbox_is_visible);
checkbox_->GetViewAccessibility().SetIsLeaf(!checkbox_is_visible);
if (link_->GetVisible()) {
trailing_content_type = views::DialogContentType::kText;
}
const LayoutProvider* provider = LayoutProvider::Get();
gfx::Insets border_insets = provider->GetDialogInsetsForContentType(
views::DialogContentType::kText, trailing_content_type);
border_insets.set_left_right(0, 0);
SetBorder(CreateEmptyBorder(border_insets));
InvalidateLayout();
}
gfx::Insets MessageBoxView::GetHorizontalInsets(
const LayoutProvider* provider) {
gfx::Insets horizontal_insets =
provider->GetInsetsMetric(views::INSETS_DIALOG);
horizontal_insets.set_top_bottom(0, 0);
return horizontal_insets;
}
BEGIN_METADATA(MessageBoxView)
END_METADATA
}