LibWeb+WebContent+Ladybird: Add ability to paste text from clipboard

Text can be pasted by pressing Ctrl/Cmd+V or by using button in the
context menu. For now only the Qt client is supported.
This commit is contained in:
Aliaksandr Kalenik 2024-03-22 11:56:49 +01:00 committed by Tim Flynn
parent d5c6e45dca
commit 561e011e07
Notes: sideshowbarker 2024-07-16 23:13:25 +09:00
15 changed files with 82 additions and 4 deletions

View file

@ -110,6 +110,12 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, WebView::Cook
edit_menu->addAction(m_copy_selection_action);
QObject::connect(m_copy_selection_action, &QAction::triggered, this, &BrowserWindow::copy_selected_text);
m_paste_action = new QAction("&Paste", this);
m_paste_action->setIcon(load_icon_from_uri("resource://icons/16x16/paste.png"sv));
m_paste_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Paste));
edit_menu->addAction(m_paste_action);
QObject::connect(m_paste_action, &QAction::triggered, this, &BrowserWindow::paste);
m_select_all_action = new QAction("Select &All", this);
m_select_all_action->setIcon(load_icon_from_uri("resource://icons/16x16/select-all.png"sv));
m_select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll));
@ -721,6 +727,15 @@ void BrowserWindow::select_all()
m_current_tab->view().select_all();
}
void BrowserWindow::paste()
{
if (!m_current_tab)
return;
auto* clipboard = QGuiApplication::clipboard();
m_current_tab->view().paste(ak_string_from_qstring(clipboard->text()));
}
void BrowserWindow::update_displayed_zoom_level()
{
VERIFY(m_current_tab);

View file

@ -59,6 +59,11 @@ public:
return *m_select_all_action;
}
QAction& paste_action()
{
return *m_paste_action;
}
QAction& view_source_action()
{
return *m_view_source_action;
@ -90,6 +95,7 @@ public slots:
void reset_zoom();
void update_zoom_menu();
void select_all();
void paste();
void copy_selected_text();
protected:
@ -131,6 +137,7 @@ private:
QAction* m_go_forward_action { nullptr };
QAction* m_reload_action { nullptr };
QAction* m_copy_selection_action { nullptr };
QAction* m_paste_action { nullptr };
QAction* m_select_all_action { nullptr };
QAction* m_view_source_action { nullptr };
QAction* m_inspect_dom_node_action { nullptr };

View file

@ -430,6 +430,7 @@ Tab::Tab(BrowserWindow* window, WebContentOptions const& web_content_options, St
m_page_context_menu->addAction(&m_window->reload_action());
m_page_context_menu->addSeparator();
m_page_context_menu->addAction(&m_window->copy_selection_action());
m_page_context_menu->addAction(&m_window->paste_action());
m_page_context_menu->addAction(&m_window->select_all_action());
m_page_context_menu->addSeparator();
m_page_context_menu->addAction(search_selected_text_action);

View file

@ -32,6 +32,7 @@ set(16x16_ICONS
layout.png
new-tab.png
open-parent-directory.png
paste.png
pause.png
play.png
select-all.png

View file

@ -441,6 +441,15 @@ void BrowsingContext::select_all()
(void)selection->select_all_children(*document->body());
}
void BrowsingContext::paste(String const& text)
{
auto* document = active_document();
if (!document)
return;
m_event_handler.handle_paste(text);
}
bool BrowsingContext::increment_cursor_position_offset()
{
if (!m_cursor_position->increment_offset())

View file

@ -130,6 +130,7 @@ public:
String selected_text() const;
void select_all();
void paste(String const&);
void did_edit(Badge<EditEventHandler>);

View file

@ -93,13 +93,20 @@ void EditEventHandler::handle_delete(DOM::Range& range)
}
void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> position, u32 code_point)
{
StringBuilder builder;
builder.append_code_point(code_point);
handle_insert(position, MUST(builder.to_string()));
}
void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> position, String data)
{
if (is<DOM::Text>(*position->node())) {
auto& node = verify_cast<DOM::Text>(*position->node());
StringBuilder builder;
builder.append(node.data().bytes_as_string_view().substring_view(0, position->offset()));
builder.append_code_point(code_point);
builder.append(data);
builder.append(node.data().bytes_as_string_view().substring_view(position->offset()));
// Cut string by max length
@ -113,9 +120,7 @@ void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> position, u
} else {
auto& node = *position->node();
auto& realm = node.realm();
StringBuilder builder;
builder.append_code_point(code_point);
auto text = realm.heap().allocate<DOM::Text>(realm, node.document(), MUST(builder.to_string()));
auto text = realm.heap().allocate<DOM::Text>(realm, node.document(), data);
MUST(node.append_child(*text));
position->set_node(text);
position->set_offset(1);

View file

@ -23,6 +23,7 @@ public:
virtual void handle_delete_character_after(JS::NonnullGCPtr<DOM::Position>);
virtual void handle_delete(DOM::Range&);
virtual void handle_insert(JS::NonnullGCPtr<DOM::Position>, u32 code_point);
virtual void handle_insert(JS::NonnullGCPtr<DOM::Position>, String);
private:
JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;

View file

@ -868,6 +868,21 @@ bool EventHandler::handle_keyup(KeyCode key, u32 modifiers, u32 code_point)
return !fire_keyboard_event(UIEvents::EventNames::keyup, m_browsing_context, key, modifiers, code_point);
}
void EventHandler::handle_paste(String const& text)
{
auto* active_document = m_browsing_context->active_document();
if (!active_document)
return;
if (!active_document->is_fully_active())
return;
if (auto cursor_position = m_browsing_context->cursor_position()) {
active_document->update_layout();
m_edit_event_handler->handle_insert(*cursor_position, text);
cursor_position->set_offset(cursor_position->offset() + text.code_points().length());
}
}
void EventHandler::set_mouse_event_tracking_paintable(Painting::Paintable* paintable)
{
m_mouse_event_tracking_paintable = paintable;

View file

@ -38,6 +38,8 @@ public:
void set_edit_event_handler(NonnullOwnPtr<EditEventHandler> value) { m_edit_event_handler = move(value); }
void handle_paste(String const& text);
void visit_edges(JS::Cell::Visitor& visitor) const;
private:

View file

@ -165,6 +165,11 @@ void ViewImplementation::select_all()
client().async_select_all(page_id());
}
void ViewImplementation::paste(String const& text)
{
client().async_paste(page_id(), text);
}
void ViewImplementation::get_source()
{
client().async_get_source(page_id());

View file

@ -63,6 +63,7 @@ public:
ByteString selected_text();
Optional<String> selected_text_with_whitespace_collapsed();
void select_all();
void paste(String const&);
void get_source();

View file

@ -910,6 +910,18 @@ void ConnectionFromClient::select_all(u64 page_id)
page.page().focused_context().select_all();
}
void ConnectionFromClient::paste(u64 page_id, String const& text)
{
auto maybe_page = page(page_id);
if (!maybe_page.has_value()) {
dbgln("ConnectionFromClient::paste: No page with ID {}", page_id);
return;
}
auto& page = maybe_page.release_value();
page.page().focused_context().paste(text);
}
Messages::WebContentServer::DumpLayoutTreeResponse ConnectionFromClient::dump_layout_tree(u64 page_id)
{
auto maybe_page = page(page_id);

View file

@ -125,6 +125,8 @@ private:
virtual Messages::WebContentServer::GetSelectedTextResponse get_selected_text(u64 page_id) override;
virtual void select_all(u64 page_id) override;
virtual void paste(u64 page_id, String const& text) override;
void report_finished_handling_input_event(u64 page_id, bool event_was_handled);
NonnullOwnPtr<PageHost> m_page_host;

View file

@ -65,6 +65,7 @@ endpoint WebContentServer
get_selected_text(u64 page_id) => (ByteString selection)
select_all(u64 page_id) =|
paste(u64 page_id, String text) =|
set_content_filters(u64 page_id, Vector<String> filters) =|
set_autoplay_allowed_on_all_websites(u64 page_id) =|