#include "ash/clipboard/clipboard_history_item.h"
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "ash/clipboard/clipboard_history_util.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/callback_list.h"
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/notreached.h"
#include "base/strings/escape.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chromeos/crosapi/mojom/clipboard_history.mojom.h"
#include "chromeos/ui/clipboard_history/clipboard_history_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/gfx/image/image.h"
#include "ui/strings/grit/ui_strings.h"
namespace ash {
namespace {
crosapi::mojom::ClipboardHistoryDisplayFormat CalculateDisplayFormat(
const ClipboardHistoryItem& item) {
switch (item.main_format()) {
case ui::ClipboardInternalFormat::kPng:
return crosapi::mojom::ClipboardHistoryDisplayFormat::kPng;
case ui::ClipboardInternalFormat::kHtml:
if (!base::Contains(item.data().markup_data(), "<img") &&
!base::Contains(item.data().markup_data(), "<table")) {
return crosapi::mojom::ClipboardHistoryDisplayFormat::kText;
}
return crosapi::mojom::ClipboardHistoryDisplayFormat::kHtml;
case ui::ClipboardInternalFormat::kText:
case ui::ClipboardInternalFormat::kSvg:
case ui::ClipboardInternalFormat::kRtf:
case ui::ClipboardInternalFormat::kBookmark:
case ui::ClipboardInternalFormat::kWeb:
return crosapi::mojom::ClipboardHistoryDisplayFormat::kText;
case ui::ClipboardInternalFormat::kFilenames:
return crosapi::mojom::ClipboardHistoryDisplayFormat::kFile;
case ui::ClipboardInternalFormat::kCustom:
return clipboard_history_util::ContainsFileSystemData(item.data())
? crosapi::mojom::ClipboardHistoryDisplayFormat::kFile
: crosapi::mojom::ClipboardHistoryDisplayFormat::kText;
}
}
std::optional<ui::ImageModel> DetermineDisplayImage(
const ClipboardHistoryItem& item) {
std::optional<ui::ImageModel> maybe_image;
switch (item.display_format()) {
case crosapi::mojom::ClipboardHistoryDisplayFormat::kUnknown:
NOTREACHED();
case crosapi::mojom::ClipboardHistoryDisplayFormat::kText:
case crosapi::mojom::ClipboardHistoryDisplayFormat::kFile:
break;
case crosapi::mojom::ClipboardHistoryDisplayFormat::kPng: {
gfx::Image image;
if (const auto& maybe_png = item.data().maybe_png()) {
image = gfx::Image::CreateFrom1xPNGBytes(maybe_png.value());
} else {
auto maybe_bitmap = item.data().GetBitmapIfPngNotEncoded();
DCHECK(maybe_bitmap.has_value());
image = gfx::Image::CreateFrom1xBitmap(maybe_bitmap.value());
}
maybe_image = ui::ImageModel::FromImage(image);
break;
}
case crosapi::mojom::ClipboardHistoryDisplayFormat::kHtml:
maybe_image = clipboard_history_util::GetHtmlPreviewPlaceholder();
break;
}
return maybe_image;
}
std::u16string DetermineDisplayTextForFileSystemData(
const ui::ClipboardData& data) {
std::u16string sources;
std::vector<std::u16string_view> source_list;
clipboard_history_util::GetSplitFileSystemData(data, &source_list, &sources);
CHECK(!sources.empty());
size_t file_count = source_list.size();
if (file_count > 1u) {
return l10n_util::GetPluralStringFUTF16(
IDS_ASH_CLIPBOARD_HISTORY_FILE_COUNT, file_count);
}
for (auto& source : source_list) {
source = source.substr(source.find_last_of(u"/") + 1);
}
return base::UTF8ToUTF16(base::UnescapeURLComponent(
base::UTF16ToUTF8(base::JoinString(source_list, u", ")),
base::UnescapeRule::SPACES));
}
std::u16string DetermineDisplayText(const ClipboardHistoryItem& item) {
switch (item.main_format()) {
case ui::ClipboardInternalFormat::kPng:
return l10n_util::GetStringUTF16(IDS_CLIPBOARD_MENU_IMAGE);
case ui::ClipboardInternalFormat::kText:
return base::UTF8ToUTF16(item.data().text());
case ui::ClipboardInternalFormat::kHtml:
if (!item.data().text().empty()) {
return base::UTF8ToUTF16(item.data().text());
}
return l10n_util::GetStringUTF16(IDS_CLIPBOARD_MENU_HTML);
case ui::ClipboardInternalFormat::kSvg:
return base::UTF8ToUTF16(item.data().svg_data());
case ui::ClipboardInternalFormat::kRtf:
return l10n_util::GetStringUTF16(IDS_CLIPBOARD_MENU_RTF_CONTENT);
case ui::ClipboardInternalFormat::kBookmark:
return base::UTF8ToUTF16(item.data().bookmark_title());
case ui::ClipboardInternalFormat::kWeb:
return l10n_util::GetStringUTF16(IDS_CLIPBOARD_MENU_WEB_SMART_PASTE);
case ui::ClipboardInternalFormat::kFilenames:
case ui::ClipboardInternalFormat::kCustom:
return DetermineDisplayTextForFileSystemData(item.data());
}
}
std::optional<gfx::ElideBehavior> DetermineDisplayTextElideBehavior(
const ClipboardHistoryItem& item) {
return chromeos::clipboard_history::IsUrl(item.display_text())
? std::make_optional(gfx::ELIDE_MIDDLE)
: std::nullopt;
}
std::optional<size_t> DetermineDisplayTextMaxLines(
const ClipboardHistoryItem& item) {
return chromeos::clipboard_history::IsUrl(item.display_text())
? std::make_optional(1u)
: std::nullopt;
}
std::optional<ui::ImageModel> DetermineIcon(const ClipboardHistoryItem& item) {
return chromeos::clipboard_history::GetIconForDescriptor(
clipboard_history_util::ItemToDescriptor(item));
}
}
ClipboardHistoryItem::ClipboardHistoryItem(ui::ClipboardData data)
: id_(base::UnguessableToken::Create()),
data_(std::move(data)),
time_copied_(base::Time::Now()),
main_format_(clipboard_history_util::CalculateMainFormat(data_).value()),
display_format_(CalculateDisplayFormat(*this)),
display_image_(DetermineDisplayImage(*this)),
display_text_(DetermineDisplayText(*this)),
display_text_elide_behavior_(DetermineDisplayTextElideBehavior(*this)),
display_text_max_lines_(DetermineDisplayTextMaxLines(*this)),
file_count_(clipboard_history_util::GetCountOfCopiedFiles(data_)),
icon_(DetermineIcon(*this)) {}
ClipboardHistoryItem::ClipboardHistoryItem(const ClipboardHistoryItem& other)
: id_(other.id_),
data_(other.data_),
time_copied_(other.time_copied_),
main_format_(other.main_format_),
display_format_(other.display_format_),
display_image_(other.display_image_),
display_text_(other.display_text_),
display_text_elide_behavior_(other.display_text_elide_behavior_),
display_text_max_lines_(other.display_text_max_lines_),
file_count_(other.file_count_),
icon_(other.icon_) {}
ClipboardHistoryItem::ClipboardHistoryItem(ClipboardHistoryItem&& other)
: id_(std::move(other.id_)),
data_(std::move(other.data_)),
time_copied_(std::move(other.time_copied_)),
main_format_(std::move(other.main_format_)),
display_format_(std::move(other.display_format_)),
display_image_(std::move(other.display_image_)),
display_text_(std::move(other.display_text_)),
display_text_elide_behavior_(
std::move(other.display_text_elide_behavior_)),
display_text_max_lines_(std::move(other.display_text_max_lines_)),
file_count_(std::move(other.file_count_)),
icon_(std::move(other.icon_)) {}
ClipboardHistoryItem::~ClipboardHistoryItem() = default;
ui::ClipboardData ClipboardHistoryItem::ReplaceEquivalentData(
ui::ClipboardData&& new_data) {
DCHECK(data_ == new_data);
time_copied_ = base::Time::Now();
if (data_.maybe_png() && !new_data.maybe_png())
new_data.SetPngDataAfterEncoding(*data_.maybe_png());
return std::exchange(data_, std::move(new_data));
}
void ClipboardHistoryItem::SetDisplayImage(
const ui::ImageModel& display_image) {
CHECK(display_image.IsImage());
display_image_ = display_image;
display_image_updated_callbacks_.Notify();
}
base::CallbackListSubscription
ClipboardHistoryItem::AddDisplayImageUpdatedCallback(
base::RepeatingClosure callback) const {
return display_image_updated_callbacks_.Add(std::move(callback));
}
}