0

Update education footer text in clipboard history menu.

When the clipboard history menu is not shown as a result of a Ctrl+V
long press event, the education footer has different text.

Note that the logic which shows the footer at the appropriate times
has not yet been implemented.

Keyboard w/ Search key: http://shortn/_GiwsaGrHPN
Keyboard w/ Launcher key, Assistant enabled: http://shortn/_l0VUEPNT4H
Keyboard w/ Launcher key, Assistant disabled: http://shortn/_cxEi9XFi0J

Bug: b:267694412
Change-Id: I0503378f3c3c3abccdb72c0ae26c39b80f1b9146
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4750340
Reviewed-by: Angus McLean <angusmclean@google.com>
Commit-Queue: David Black <dmblack@google.com>
Cr-Commit-Position: refs/heads/main@{#1179364}
This commit is contained in:
David Black
2023-08-04 00:27:04 +00:00
committed by Chromium LUCI CQ
parent 56e579403a
commit 5a76f69a71
7 changed files with 127 additions and 24 deletions

@@ -6028,6 +6028,9 @@ Here are some things you can try to get started.
<message name="IDS_ASH_CLIPBOARD_HISTORY_CONTROL_V_LONGPRESS_FOOTER" desc="The message used to educate users about the clipboard history menu that shows when they hold down Ctrl+V."> <message name="IDS_ASH_CLIPBOARD_HISTORY_CONTROL_V_LONGPRESS_FOOTER" desc="The message used to educate users about the clipboard history menu that shows when they hold down Ctrl+V.">
Youll see the clipboard when you press and hold Ctrl + V. You can turn off this shortcut by disabling the #clipboard-history-longpress flag in chrome://flags (os://flags if using Lacros). Youll see the clipboard when you press and hold Ctrl + V. You can turn off this shortcut by disabling the #clipboard-history-longpress flag in chrome://flags (os://flags if using Lacros).
</message> </message>
<message name="IDS_ASH_CLIPBOARD_HISTORY_FOOTER" desc="The message used to educate users about the clipboard history menu that shows when they use the feature for the first time (or for the first time in a long time).">
Select an item to paste it. You can see the clipboard by pressing <ph name="SHORTCUT_KEY_NAME">$1<ex>Launcher</ex></ph> + v.
</message>
<message name="IDS_ASH_CLIPBOARD_HISTORY_HEADER_TITLE" desc="The title of the ChromeOS clipboard history menu, which shows the user's most recently copied items."> <message name="IDS_ASH_CLIPBOARD_HISTORY_HEADER_TITLE" desc="The title of the ChromeOS clipboard history menu, which shows the user's most recently copied items.">
Clipboard Clipboard
</message> </message>

@@ -0,0 +1 @@
2a12f38fbc26673c249afd40154e9b401f013880

@@ -57,6 +57,11 @@ namespace ash {
namespace { namespace {
// Returns a font list resolved from the specified `typography_token`.
gfx::FontList Resolve(TypographyToken typography_token) {
return TypographyProvider::Get()->ResolveTypographyToken(typography_token);
}
// Returns whether the clipboard history menu requires a header. // Returns whether the clipboard history menu requires a header.
bool IsHeaderRequired() { bool IsHeaderRequired() {
return chromeos::features::IsClipboardHistoryRefreshEnabled(); return chromeos::features::IsClipboardHistoryRefreshEnabled();
@@ -94,10 +99,74 @@ void InsertHeaderContent(views::MenuItemView* container) {
} }
// TODO(http://b/267694412): Add pixel test. // TODO(http://b/267694412): Add pixel test.
// Populates `container` with education content to appear at the bottom of the // Populates `styled_label` with educational text to appear at the bottom of the
// clipboard history menu. This method may only be called when clipboard history // clipboard history menu. This method may only be called when clipboard history
// refresh is enabled. // refresh is enabled.
void InsertFooterContentV2(views::MenuItemView* container) { void InsertFooterContentV2LabelStyledText(
crosapi::mojom::ClipboardHistoryControllerShowSource show_source,
views::StyledLabel* styled_label) {
CHECK(chromeos::features::IsClipboardHistoryRefreshEnabled());
// Create text style.
views::StyledLabel::RangeStyleInfo text_style;
text_style.custom_font = Resolve(TypographyToken::kCrosAnnotation1);
text_style.override_color_id = cros_tokens::kCrosSysOnSurfaceVariant;
// When the clipboard history menu is shown from a Ctrl+V long press event, a
// specific educational text is used which does not require inline icons.
using crosapi::mojom::ClipboardHistoryControllerShowSource;
if (show_source == ClipboardHistoryControllerShowSource::kControlVLongpress) {
styled_label->SetText(l10n_util::GetStringUTF16(
IDS_ASH_CLIPBOARD_HISTORY_CONTROL_V_LONGPRESS_FOOTER));
styled_label->AddStyleRange(gfx::Range(0u, styled_label->GetText().size()),
std::move(text_style));
return;
}
// When the clipboard history menu is *not* shown from a Ctrl+V long press
// event, set text based on keyboard layout, caching the offset where an
// inline icon should be inserted.
size_t inline_icon_offset;
const auto& shortcut_key = clipboard_history_util::GetShortcutKeyName();
styled_label->SetText(l10n_util::GetStringFUTF16(
IDS_ASH_CLIPBOARD_HISTORY_FOOTER, shortcut_key, &inline_icon_offset));
inline_icon_offset += shortcut_key.size();
// Set text styles.
styled_label->AddStyleRange(gfx::Range(0u, inline_icon_offset), text_style);
styled_label->AddStyleRange(
gfx::Range(inline_icon_offset + 1u, styled_label->GetText().size()),
std::move(text_style));
// Create inline icon.
auto inline_icon =
views::Builder<views::ImageView>()
.SetBorder(views::CreateEmptyBorder(
ClipboardHistoryViews::kInlineIconMargins))
.SetImage(ui::ImageModel::FromVectorIcon(
clipboard_history_util::GetShortcutKeyIcon(),
cros_tokens::kCrosSysOnSurfaceVariant,
ClipboardHistoryViews::kFooterContentV2InlineIconSize))
.Build();
// Insert inline icon.
views::StyledLabel::RangeStyleInfo inline_icon_style;
inline_icon_style.custom_view = inline_icon.get();
styled_label->AddStyleRange(
gfx::Range(inline_icon_offset, inline_icon_offset + 1u),
std::move(inline_icon_style));
// Transfer inline icon ownership.
styled_label->AddCustomView(std::move(inline_icon));
}
// TODO(http://b/267694412): Add pixel test.
// Populates `container` with educational content to appear at the bottom of the
// clipboard history menu. This method may only be called when clipboard history
// refresh is enabled.
void InsertFooterContentV2(
views::MenuItemView* container,
crosapi::mojom::ClipboardHistoryControllerShowSource show_source) {
CHECK(chromeos::features::IsClipboardHistoryRefreshEnabled()); CHECK(chromeos::features::IsClipboardHistoryRefreshEnabled());
// Cache `menu_padding`. // Cache `menu_padding`.
@@ -132,38 +201,25 @@ void InsertFooterContentV2(views::MenuItemView* container) {
ClipboardHistoryViews::kFooterContentV2IconSize)), ClipboardHistoryViews::kFooterContentV2IconSize)),
views::Builder<views::StyledLabel>() views::Builder<views::StyledLabel>()
.SetAutoColorReadabilityEnabled(false) .SetAutoColorReadabilityEnabled(false)
.SetText(l10n_util::GetStringUTF16( .SetID(clipboard_history_util::kFooterContentV2LabelID)
IDS_ASH_CLIPBOARD_HISTORY_CONTROL_V_LONGPRESS_FOOTER))
.SizeToFit( .SizeToFit(
footer_width - footer_width -
ClipboardHistoryViews::kFooterContentV2Insets.width() - ClipboardHistoryViews::kFooterContentV2Insets.width() -
ClipboardHistoryViews::kFooterContentV2IconSize - ClipboardHistoryViews::kFooterContentV2IconSize -
ClipboardHistoryViews::kFooterContentV2ChildSpacing) ClipboardHistoryViews::kFooterContentV2ChildSpacing)
.CustomConfigure( .CustomConfigure(base::BindOnce(
base::BindOnce([](views::StyledLabel* label) { &InsertFooterContentV2LabelStyledText, show_source)))
views::StyledLabel::RangeStyleInfo style;
style.custom_font =
TypographyProvider::Get()->ResolveTypographyToken(
TypographyToken::kCrosAnnotation1);
style.override_color_id =
cros_tokens::kCrosSysOnSurfaceVariant;
// NOTE: `SetText()` must precede `AddStyleRange()`.
label->AddStyleRange(
gfx::Range(0u, label->GetText().size()),
std::move(style));
})))
.Build()); .Build());
} }
// TODO(http://b/267694412): Add pixel test. // TODO(http://b/267694412): Add pixel test.
// Populates `container` with educational content to appear at the bottom of the // Populates `container` with educational content to appear at the bottom of the
// clipboard history menu. // clipboard history menu.
void InsertFooterContent(views::MenuItemView* container) { void InsertFooterContent(
views::MenuItemView* container,
crosapi::mojom::ClipboardHistoryControllerShowSource show_source) {
if (chromeos::features::IsClipboardHistoryRefreshEnabled()) { if (chromeos::features::IsClipboardHistoryRefreshEnabled()) {
InsertFooterContentV2(container); InsertFooterContentV2(container, show_source);
return; return;
} }
@@ -286,6 +342,7 @@ void ClipboardHistoryMenuModelAdapter::Run(
run_before_ = true; run_before_ = true;
menu_open_time_ = base::TimeTicks::Now(); menu_open_time_ = base::TimeTicks::Now();
menu_show_source_ = show_source;
int command_id = clipboard_history_util::kFirstItemCommandId; int command_id = clipboard_history_util::kFirstItemCommandId;
const auto& items = clipboard_history_->GetItems(); const auto& items = clipboard_history_->GetItems();
@@ -642,7 +699,7 @@ views::MenuItemView* ClipboardHistoryMenuModelAdapter::AppendMenuItem(
InsertHeaderContent(container); InsertHeaderContent(container);
} else if (footer_index_ == index) { } else if (footer_index_ == index) {
CHECK_EQ(model->GetTypeAt(index), ui::MenuModel::ItemType::TYPE_TITLE); CHECK_EQ(model->GetTypeAt(index), ui::MenuModel::ItemType::TYPE_TITLE);
InsertFooterContent(container); InsertFooterContent(container, menu_show_source_.value());
} else { } else {
CHECK_EQ(model->GetTypeAt(index), ui::MenuModel::ItemType::TYPE_COMMAND); CHECK_EQ(model->GetTypeAt(index), ui::MenuModel::ItemType::TYPE_COMMAND);
std::unique_ptr<ClipboardHistoryItemView> item_view = std::unique_ptr<ClipboardHistoryItemView> item_view =

@@ -146,6 +146,10 @@ class ASH_EXPORT ClipboardHistoryMenuModelAdapter
// The timestamp taken when the menu is opened. Used in metrics. // The timestamp taken when the menu is opened. Used in metrics.
base::TimeTicks menu_open_time_; base::TimeTicks menu_open_time_;
// The source which opened the menu, absent until the menu is `Run()`.
absl::optional<crosapi::mojom::ClipboardHistoryControllerShowSource>
menu_show_source_;
// The mapping between the command ids and items that are copied from // The mapping between the command ids and items that are copied from
// `clipboard_history_` when the menu is created. It is used to solve the // `clipboard_history_` when the menu is created. It is used to solve the
// possible inconsistency between the menu model data and the clipboard // possible inconsistency between the menu model data and the clipboard

@@ -4,12 +4,15 @@
#include "ash/clipboard/clipboard_history_menu_model_adapter.h" #include "ash/clipboard/clipboard_history_menu_model_adapter.h"
#include <string>
#include "ash/clipboard/clipboard_history.h" #include "ash/clipboard/clipboard_history.h"
#include "ash/clipboard/clipboard_history_controller_impl.h" #include "ash/clipboard/clipboard_history_controller_impl.h"
#include "ash/clipboard/clipboard_history_util.h" #include "ash/clipboard/clipboard_history_util.h"
#include "ash/clipboard/views/clipboard_history_view_constants.h" #include "ash/clipboard/views/clipboard_history_view_constants.h"
#include "ash/constants/ash_features.h" #include "ash/constants/ash_features.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/test/ash_test_base.h" #include "ash/test/ash_test_base.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h" #include "base/test/test_future.h"
@@ -19,11 +22,13 @@
#include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/simple_menu_model.h" #include "ui/base/models/simple_menu_model.h"
#include "ui/base/ui_base_types.h" #include "ui/base/ui_base_types.h"
#include "ui/gfx/text_constants.h" #include "ui/gfx/text_constants.h"
#include "ui/views/controls/label.h" #include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/view_utils.h" #include "ui/views/view_utils.h"
namespace ash { namespace ash {
@@ -36,6 +41,7 @@ using ::testing::Eq;
using ::testing::IsNull; using ::testing::IsNull;
using ::testing::NotNull; using ::testing::NotNull;
using ::testing::Property; using ::testing::Property;
using ::testing::ResultOf;
using ::testing::ValuesIn; using ::testing::ValuesIn;
using ::testing::WithParamInterface; using ::testing::WithParamInterface;
@@ -43,6 +49,8 @@ using crosapi::mojom::ClipboardHistoryControllerShowSource;
namespace { namespace {
// Helpers ---------------------------------------------------------------------
ClipboardHistoryControllerImpl* GetClipboardHistoryController() { ClipboardHistoryControllerImpl* GetClipboardHistoryController() {
return Shell::Get()->clipboard_history_controller(); return Shell::Get()->clipboard_history_controller();
} }
@@ -66,8 +74,21 @@ void FlushMessageLoop() {
run_loop.Run(); run_loop.Run();
} }
// Matchers --------------------------------------------------------------------
template <typename ViewType, typename MatcherType>
auto GetViewById(int id, MatcherType m) {
return ResultOf(
[id](const auto* arg) {
return views::AsViewClass<ViewType>(arg->GetViewByID(id));
},
m);
}
} // namespace } // namespace
// ClipboardHistoryMenuModelAdapterRefreshTest ---------------------------------
// Base class for `ClipboardHistoryMenuModelAdapter` tests whose only required // Base class for `ClipboardHistoryMenuModelAdapter` tests whose only required
// parameterization is whether the clipboard history refresh is enabled. // parameterization is whether the clipboard history refresh is enabled.
class ClipboardHistoryMenuModelAdapterRefreshTest class ClipboardHistoryMenuModelAdapterRefreshTest
@@ -321,7 +342,20 @@ TEST_P(ClipboardHistoryMenuModelAdapterMenuItemTest,
Conditional(IsClipboardHistoryRefreshEnabled(), IsNull(), NotNull())); Conditional(IsClipboardHistoryRefreshEnabled(), IsNull(), NotNull()));
EXPECT_THAT( EXPECT_THAT(
footer->GetViewByID(clipboard_history_util::kFooterContentV2ViewID), footer->GetViewByID(clipboard_history_util::kFooterContentV2ViewID),
Conditional(IsClipboardHistoryRefreshEnabled(), NotNull(), IsNull())); Conditional(
IsClipboardHistoryRefreshEnabled(),
GetViewById<views::StyledLabel>(
clipboard_history_util::kFooterContentV2LabelID,
Property(
&views::StyledLabel::GetText,
Conditional(
IsClipboardHistoryLongpressEnabled(),
l10n_util::GetStringUTF16(
IDS_ASH_CLIPBOARD_HISTORY_CONTROL_V_LONGPRESS_FOOTER),
l10n_util::GetStringFUTF16(
IDS_ASH_CLIPBOARD_HISTORY_FOOTER,
clipboard_history_util::GetShortcutKeyName())))),
IsNull()));
} }
} // namespace ash } // namespace ash

@@ -63,6 +63,7 @@ enum MenuViewID {
kDeleteButtonViewID, kDeleteButtonViewID,
kDisplayTextLabelID, kDisplayTextLabelID,
kFooterContentViewID, kFooterContentViewID,
kFooterContentV2LabelID,
kFooterContentV2ViewID, kFooterContentV2ViewID,
kSecondaryDisplayTextLabelID, kSecondaryDisplayTextLabelID,
}; };

@@ -25,6 +25,9 @@ constexpr int kFooterContentV2ChildSpacing = 6;
// The icon size for footer content v2. // The icon size for footer content v2.
constexpr int kFooterContentV2IconSize = 20; constexpr int kFooterContentV2IconSize = 20;
// The inline icon size for footer content v2.
constexpr int kFooterContentV2InlineIconSize = 16;
// The insets for footer content v2. // The insets for footer content v2.
constexpr auto kFooterContentV2Insets = gfx::Insets(8); constexpr auto kFooterContentV2Insets = gfx::Insets(8);