Commit 3c751ff1 authored by Andrew Xu's avatar Andrew Xu Committed by Commit Bot

[Multipaste] Implement the item deletion through the backspace key

This CL implements the feature that the selected item is able to be
deleted by pressing the backspace key. If no item is selected, pressing
the backspace key does nothing.

Bug: 1114773
Change-Id: I179050d4c15b9ef93b3b4da2e9bbc8395bf3a0fd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2390859Reviewed-by: default avatarDavid Black <dmblack@google.com>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Commit-Queue: Andrew Xu <andrewxu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805528}
parent 116d3648
...@@ -55,27 +55,45 @@ class ClipboardHistoryController::AcceleratorTarget ...@@ -55,27 +55,45 @@ class ClipboardHistoryController::AcceleratorTarget
: public ui::AcceleratorTarget { : public ui::AcceleratorTarget {
public: public:
explicit AcceleratorTarget(ClipboardHistoryController* controller) explicit AcceleratorTarget(ClipboardHistoryController* controller)
: controller_(controller) {} : controller_(controller),
show_menu_combo_(
ui::Accelerator(/*key_code=*/ui::VKEY_V,
/*modifiers=*/ui::EF_COMMAND_DOWN,
/*key_state=*/ui::Accelerator::KeyState::PRESSED)),
delete_selected_(ui::Accelerator(
/*key_code=*/ui::VKEY_BACK,
/*modifiers=*/ui::EF_NONE,
/*key_state=*/ui::Accelerator::KeyState::PRESSED)) {}
AcceleratorTarget(const AcceleratorTarget&) = delete; AcceleratorTarget(const AcceleratorTarget&) = delete;
AcceleratorTarget& operator=(const AcceleratorTarget&) = delete; AcceleratorTarget& operator=(const AcceleratorTarget&) = delete;
~AcceleratorTarget() override = default; ~AcceleratorTarget() override = default;
void Init() { void Init() {
ui::Accelerator show_menu_combo(ui::VKEY_V, ui::EF_COMMAND_DOWN);
show_menu_combo.set_key_state(ui::Accelerator::KeyState::PRESSED);
// Register, but no need to unregister because this outlives // Register, but no need to unregister because this outlives
// AcceleratorController. // AcceleratorController.
Shell::Get()->accelerator_controller()->Register( Shell::Get()->accelerator_controller()->Register(
{show_menu_combo}, /*accelerator_target=*/this); {show_menu_combo_}, /*accelerator_target=*/this);
}
void OnMenuShown() {
Shell::Get()->accelerator_controller()->Register(
{delete_selected_}, /*accelerator_target=*/this);
}
void OnMenuClosed() {
Shell::Get()->accelerator_controller()->Unregister(
{delete_selected_}, /*accelerator_target=*/this);
} }
private: private:
// ui::AcceleratorTarget: // ui::AcceleratorTarget:
bool AcceleratorPressed(const ui::Accelerator& accelerator) override { bool AcceleratorPressed(const ui::Accelerator& accelerator) override {
if (controller_->IsMenuShowing()) DCHECK(accelerator == show_menu_combo_ || accelerator == delete_selected_);
controller_->ExecuteSelectedMenuItem(accelerator.modifiers());
if (accelerator == show_menu_combo_)
HandleShowMenuCombo();
else else
controller_->ShowMenu(); HandleDeleteSelected();
return true; return true;
} }
...@@ -83,8 +101,27 @@ class ClipboardHistoryController::AcceleratorTarget ...@@ -83,8 +101,27 @@ class ClipboardHistoryController::AcceleratorTarget
return controller_->IsMenuShowing() || controller_->CanShowMenu(); return controller_->IsMenuShowing() || controller_->CanShowMenu();
} }
void HandleShowMenuCombo() {
if (controller_->IsMenuShowing())
controller_->ExecuteSelectedMenuItem(show_menu_combo_.modifiers());
else
controller_->ShowMenu();
}
void HandleDeleteSelected() {
DCHECK(controller_->IsMenuShowing());
controller_->DeleteSelectedMenuItemIfAny();
}
// The controller responsible for showing the Clipboard History menu. // The controller responsible for showing the Clipboard History menu.
ClipboardHistoryController* const controller_; ClipboardHistoryController* const controller_;
// The accelerator to show the menu or execute the selected menu item.
const ui::Accelerator show_menu_combo_;
// The accelerator to delete the selected menu item. It is only registered
// while the menu is showing.
const ui::Accelerator delete_selected_;
}; };
// ClipboardHistoryController::MenuDelegate ------------------------------------ // ClipboardHistoryController::MenuDelegate ------------------------------------
...@@ -100,7 +137,7 @@ class ClipboardHistoryController::MenuDelegate ...@@ -100,7 +137,7 @@ class ClipboardHistoryController::MenuDelegate
// ui::SimpleMenuModel::Delegate: // ui::SimpleMenuModel::Delegate:
void ExecuteCommand(int command_id, int event_flags) override { void ExecuteCommand(int command_id, int event_flags) override {
if (command_id == ClipboardHistoryUtil::kDeleteCommandId) { if (command_id == ClipboardHistoryUtil::kDeleteCommandId) {
controller_->DeleteSelectedMenuItem(); controller_->DeleteSelectedMenuItemIfAny();
return; return;
} }
...@@ -155,8 +192,14 @@ void ClipboardHistoryController::ShowMenu() { ...@@ -155,8 +192,14 @@ void ClipboardHistoryController::ShowMenu() {
return; return;
context_menu_ = ClipboardHistoryMenuModelAdapter::Create( context_menu_ = ClipboardHistoryMenuModelAdapter::Create(
menu_delegate_.get(), clipboard_history_.get()); menu_delegate_.get(),
base::BindRepeating(&ClipboardHistoryController::OnMenuClosed,
base::Unretained(this)),
clipboard_history_.get());
context_menu_->Run(CalculateAnchorRect()); context_menu_->Run(CalculateAnchorRect());
DCHECK(IsMenuShowing());
accelerator_target_->OnMenuShown();
} }
void ClipboardHistoryController::MenuOptionSelected(int command_id, void ClipboardHistoryController::MenuOptionSelected(int command_id,
...@@ -232,11 +275,14 @@ void ClipboardHistoryController::MenuOptionSelected(int command_id, ...@@ -232,11 +275,14 @@ void ClipboardHistoryController::MenuOptionSelected(int command_id,
base::TimeDelta::FromMilliseconds(200)); base::TimeDelta::FromMilliseconds(200));
} }
void ClipboardHistoryController::DeleteSelectedMenuItem() { void ClipboardHistoryController::DeleteSelectedMenuItemIfAny() {
DCHECK(context_menu_); DCHECK(context_menu_);
auto selected_command = context_menu_->GetSelectedMenuItemCommand(); auto selected_command = context_menu_->GetSelectedMenuItemCommand();
DCHECK(selected_command.has_value()); // Return early if no item is selected.
if (!selected_command.has_value())
return;
DCHECK_GE(*selected_command, ClipboardHistoryUtil::kFirstItemCommandId); DCHECK_GE(*selected_command, ClipboardHistoryUtil::kFirstItemCommandId);
clipboard_history_->RemoveItemForId( clipboard_history_->RemoveItemForId(
...@@ -285,4 +331,8 @@ gfx::Rect ClipboardHistoryController::CalculateAnchorRect() const { ...@@ -285,4 +331,8 @@ gfx::Rect ClipboardHistoryController::CalculateAnchorRect() const {
gfx::Size()); gfx::Size());
} }
void ClipboardHistoryController::OnMenuClosed() {
accelerator_target_->OnMenuClosed();
}
} // namespace ash } // namespace ash
...@@ -54,6 +54,10 @@ class ASH_EXPORT ClipboardHistoryController { ...@@ -54,6 +54,10 @@ class ASH_EXPORT ClipboardHistoryController {
return nudge_controller_.get(); return nudge_controller_.get();
} }
const ClipboardHistoryMenuModelAdapter* context_menu_for_test() const {
return context_menu_.get();
}
private: private:
class AcceleratorTarget; class AcceleratorTarget;
class MenuDelegate; class MenuDelegate;
...@@ -63,11 +67,15 @@ class ASH_EXPORT ClipboardHistoryController { ...@@ -63,11 +67,15 @@ class ASH_EXPORT ClipboardHistoryController {
void ExecuteSelectedMenuItem(int event_flags); void ExecuteSelectedMenuItem(int event_flags);
void MenuOptionSelected(int command_id, int event_flags); void MenuOptionSelected(int command_id, int event_flags);
// Delete the menu item being selected and its corresponding data. // Delete the menu item being selected and its corresponding data. If no item
void DeleteSelectedMenuItem(); // is selected, do nothing.
void DeleteSelectedMenuItemIfAny();
gfx::Rect CalculateAnchorRect() const; gfx::Rect CalculateAnchorRect() const;
// Called when the contextual menu is closed.
void OnMenuClosed();
// The menu being shown. // The menu being shown.
std::unique_ptr<ClipboardHistoryMenuModelAdapter> context_menu_; std::unique_ptr<ClipboardHistoryMenuModelAdapter> context_menu_;
// Used to keep track of what is being copied to the clipboard. // Used to keep track of what is being copied to the clipboard.
......
...@@ -5,11 +5,9 @@ ...@@ -5,11 +5,9 @@
#include "ash/clipboard/clipboard_history_menu_model_adapter.h" #include "ash/clipboard/clipboard_history_menu_model_adapter.h"
#include "ash/clipboard/clipboard_history.h" #include "ash/clipboard/clipboard_history.h"
#include "ash/clipboard/clipboard_history_controller.h"
#include "ash/clipboard/clipboard_history_util.h" #include "ash/clipboard/clipboard_history_util.h"
#include "ash/clipboard/views/clipboard_history_item_view.h" #include "ash/clipboard/views/clipboard_history_item_view.h"
#include "ash/public/cpp/clipboard_image_model_factory.h" #include "ash/public/cpp/clipboard_image_model_factory.h"
#include "ash/shell.h"
#include "ui/base/ui_base_types.h" #include "ui/base/ui_base_types.h"
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/menu_item_view.h"
...@@ -23,9 +21,11 @@ namespace ash { ...@@ -23,9 +21,11 @@ namespace ash {
std::unique_ptr<ClipboardHistoryMenuModelAdapter> std::unique_ptr<ClipboardHistoryMenuModelAdapter>
ClipboardHistoryMenuModelAdapter::Create( ClipboardHistoryMenuModelAdapter::Create(
ui::SimpleMenuModel::Delegate* delegate, ui::SimpleMenuModel::Delegate* delegate,
base::RepeatingClosure menu_closed_callback,
const ClipboardHistory* clipboard_history) { const ClipboardHistory* clipboard_history) {
return base::WrapUnique(new ClipboardHistoryMenuModelAdapter( return base::WrapUnique(new ClipboardHistoryMenuModelAdapter(
std::make_unique<ui::SimpleMenuModel>(delegate), clipboard_history)); std::make_unique<ui::SimpleMenuModel>(delegate),
std::move(menu_closed_callback), clipboard_history));
} }
ClipboardHistoryMenuModelAdapter::~ClipboardHistoryMenuModelAdapter() = default; ClipboardHistoryMenuModelAdapter::~ClipboardHistoryMenuModelAdapter() = default;
...@@ -70,9 +70,12 @@ void ClipboardHistoryMenuModelAdapter::Cancel() { ...@@ -70,9 +70,12 @@ void ClipboardHistoryMenuModelAdapter::Cancel() {
base::Optional<int> base::Optional<int>
ClipboardHistoryMenuModelAdapter::GetSelectedMenuItemCommand() const { ClipboardHistoryMenuModelAdapter::GetSelectedMenuItemCommand() const {
DCHECK(root_view_); DCHECK(root_view_);
// `root_view_` may be selected if no menu item is under selection.
auto* menu_item = root_view_->GetMenuController()->GetSelectedMenuItem(); auto* menu_item = root_view_->GetMenuController()->GetSelectedMenuItem();
return menu_item ? base::make_optional(menu_item->GetCommand()) return menu_item && menu_item != root_view_
: base::nullopt; ? base::make_optional(menu_item->GetCommand())
: base::nullopt;
} }
const ClipboardHistoryItem& const ClipboardHistoryItem&
...@@ -101,8 +104,9 @@ gfx::Rect ClipboardHistoryMenuModelAdapter::GetMenuBoundsInScreenForTest() ...@@ -101,8 +104,9 @@ gfx::Rect ClipboardHistoryMenuModelAdapter::GetMenuBoundsInScreenForTest()
ClipboardHistoryMenuModelAdapter::ClipboardHistoryMenuModelAdapter( ClipboardHistoryMenuModelAdapter::ClipboardHistoryMenuModelAdapter(
std::unique_ptr<ui::SimpleMenuModel> model, std::unique_ptr<ui::SimpleMenuModel> model,
base::RepeatingClosure menu_closed_callback,
const ClipboardHistory* clipboard_history) const ClipboardHistory* clipboard_history)
: views::MenuModelAdapter(model.get()), : views::MenuModelAdapter(model.get(), std::move(menu_closed_callback)),
model_(std::move(model)), model_(std::move(model)),
clipboard_history_(clipboard_history) {} clipboard_history_(clipboard_history) {}
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <memory> #include <memory>
#include "ash/ash_export.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/unguessable_token.h" #include "base/unguessable_token.h"
#include "ui/base/models/simple_menu_model.h" #include "ui/base/models/simple_menu_model.h"
...@@ -28,10 +29,11 @@ class ClipboardHistory; ...@@ -28,10 +29,11 @@ class ClipboardHistory;
// Used to show the clipboard history menu, which holds the last few things // Used to show the clipboard history menu, which holds the last few things
// copied. // copied.
class ClipboardHistoryMenuModelAdapter : views::MenuModelAdapter { class ASH_EXPORT ClipboardHistoryMenuModelAdapter : views::MenuModelAdapter {
public: public:
static std::unique_ptr<ClipboardHistoryMenuModelAdapter> Create( static std::unique_ptr<ClipboardHistoryMenuModelAdapter> Create(
ui::SimpleMenuModel::Delegate* delegate, ui::SimpleMenuModel::Delegate* delegate,
base::RepeatingClosure menu_closed_callback,
const ClipboardHistory* clipboard_history); const ClipboardHistory* clipboard_history);
ClipboardHistoryMenuModelAdapter(const ClipboardHistoryMenuModelAdapter&) = ClipboardHistoryMenuModelAdapter(const ClipboardHistoryMenuModelAdapter&) =
...@@ -66,8 +68,10 @@ class ClipboardHistoryMenuModelAdapter : views::MenuModelAdapter { ...@@ -66,8 +68,10 @@ class ClipboardHistoryMenuModelAdapter : views::MenuModelAdapter {
gfx::Rect GetMenuBoundsInScreenForTest() const; gfx::Rect GetMenuBoundsInScreenForTest() const;
private: private:
ClipboardHistoryMenuModelAdapter(std::unique_ptr<ui::SimpleMenuModel> model, ClipboardHistoryMenuModelAdapter(
const ClipboardHistory* clipboard_history); std::unique_ptr<ui::SimpleMenuModel> model,
base::RepeatingClosure menu_closed_callback,
const ClipboardHistory* clipboard_history);
// views::MenuModelAdapter: // views::MenuModelAdapter:
views::MenuItemView* AppendMenuItem(views::MenuItemView* menu, views::MenuItemView* AppendMenuItem(views::MenuItemView* menu,
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "ash/clipboard/clipboard_history.h" #include "ash/clipboard/clipboard_history.h"
#include "ash/clipboard/clipboard_history_controller.h" #include "ash/clipboard/clipboard_history_controller.h"
#include "ash/clipboard/clipboard_history_item.h" #include "ash/clipboard/clipboard_history_item.h"
#include "ash/clipboard/clipboard_history_menu_model_adapter.h"
#include "ash/shell.h" #include "ash/shell.h"
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
...@@ -76,6 +77,10 @@ ash::ClipboardHistoryController* GetClipboardHistoryController() { ...@@ -76,6 +77,10 @@ ash::ClipboardHistoryController* GetClipboardHistoryController() {
return ash::Shell::Get()->clipboard_history_controller(); return ash::Shell::Get()->clipboard_history_controller();
} }
const ash::ClipboardHistoryMenuModelAdapter* GetContextMenu() {
return GetClipboardHistoryController()->context_menu_for_test();
}
const std::list<ash::ClipboardHistoryItem>& GetClipboardItems() { const std::list<ash::ClipboardHistoryItem>& GetClipboardItems() {
return GetClipboardHistoryController()->history()->GetItems(); return GetClipboardHistoryController()->history()->GetItems();
} }
...@@ -84,6 +89,23 @@ gfx::Rect GetClipboardHistoryMenuBoundsInScreen() { ...@@ -84,6 +89,23 @@ gfx::Rect GetClipboardHistoryMenuBoundsInScreen() {
return GetClipboardHistoryController()->GetMenuBoundsInScreenForTest(); return GetClipboardHistoryController()->GetMenuBoundsInScreenForTest();
} }
bool VerifyClipboardTextData(const std::initializer_list<std::string>& texts) {
const std::list<ash::ClipboardHistoryItem>& items = GetClipboardItems();
if (items.size() != texts.size())
return false;
auto items_iter = items.cbegin();
const auto* texts_iter = texts.begin();
while (items_iter != items.cend() && texts_iter != texts.end()) {
if (items_iter->data().text() != *texts_iter)
return false;
++items_iter;
++texts_iter;
}
return true;
}
} // namespace } // namespace
// Verify clipboard history's features in the multiprofile environment. // Verify clipboard history's features in the multiprofile environment.
...@@ -353,6 +375,52 @@ IN_PROC_BROWSER_TEST_F(ClipboardHistoryWithMultiProfileBrowserTest, ...@@ -353,6 +375,52 @@ IN_PROC_BROWSER_TEST_F(ClipboardHistoryWithMultiProfileBrowserTest,
Release(ui::KeyboardCode::VKEY_COMMAND); Release(ui::KeyboardCode::VKEY_COMMAND);
} }
// Verifies that the selected item should be deleted by the backspace key.
IN_PROC_BROWSER_TEST_F(ClipboardHistoryWithMultiProfileBrowserTest,
DeleteItemViaBackspaceKey) {
LoginUser(account_id1_);
// Write some things to the clipboard.
SetClipboardText("A");
SetClipboardText("B");
SetClipboardText("C");
// Show the menu.
ShowContextMenuViaAccelerator();
ASSERT_TRUE(GetClipboardHistoryController()->IsMenuShowing());
ASSERT_EQ(3, GetContextMenu()->GetMenuItemsCount());
// No item is selected. So pressing the backspace key has no effect.
PressAndRelease(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_EQ(3, GetContextMenu()->GetMenuItemsCount());
EXPECT_TRUE(VerifyClipboardTextData({"C", "B", "A"}));
// Select the first menu item via key then delete it. Verify the menu and the
// clipboard history.
PressAndRelease(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
PressAndRelease(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_EQ(2, GetContextMenu()->GetMenuItemsCount());
EXPECT_TRUE(VerifyClipboardTextData({"B", "A"}));
// Select the second menu item via key then delete it. Verify the menu and the
// clipboard history.
PressAndRelease(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
PressAndRelease(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
PressAndRelease(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_EQ(1, GetContextMenu()->GetMenuItemsCount());
EXPECT_TRUE(VerifyClipboardTextData({"B"}));
// Delete the last item. Verify that the menu is closed.
PressAndRelease(ui::KeyboardCode::VKEY_DOWN, ui::EF_COMMAND_DOWN);
PressAndRelease(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_FALSE(GetClipboardHistoryController()->IsMenuShowing());
// Trigger the accelerator of opening the clipboard history menu. No menu
// shows because of the empty history data.
ShowContextMenuViaAccelerator();
EXPECT_FALSE(GetClipboardHistoryController()->IsMenuShowing());
}
// Flaky: crbug.com/1123542 // Flaky: crbug.com/1123542
IN_PROC_BROWSER_TEST_F(ClipboardHistoryWithMultiProfileBrowserTest, IN_PROC_BROWSER_TEST_F(ClipboardHistoryWithMultiProfileBrowserTest,
DISABLED_ShouldPasteHistoryAsPlainText) { DISABLED_ShouldPasteHistoryAsPlainText) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment