mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-27 21:42:53 +00:00
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:
parent
d5c6e45dca
commit
561e011e07
Notes:
sideshowbarker
2024-07-16 23:13:25 +09:00
Author: https://github.com/kalenikaliaksandr
Commit: 561e011e07
Pull-request: https://github.com/SerenityOS/serenity/pull/23675
Reviewed-by: https://github.com/trflynn89
15 changed files with 82 additions and 4 deletions
|
@ -110,6 +110,12 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, WebView::Cook
|
||||||
edit_menu->addAction(m_copy_selection_action);
|
edit_menu->addAction(m_copy_selection_action);
|
||||||
QObject::connect(m_copy_selection_action, &QAction::triggered, this, &BrowserWindow::copy_selected_text);
|
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 = 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->setIcon(load_icon_from_uri("resource://icons/16x16/select-all.png"sv));
|
||||||
m_select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll));
|
m_select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll));
|
||||||
|
@ -721,6 +727,15 @@ void BrowserWindow::select_all()
|
||||||
m_current_tab->view().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()
|
void BrowserWindow::update_displayed_zoom_level()
|
||||||
{
|
{
|
||||||
VERIFY(m_current_tab);
|
VERIFY(m_current_tab);
|
||||||
|
|
|
@ -59,6 +59,11 @@ public:
|
||||||
return *m_select_all_action;
|
return *m_select_all_action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QAction& paste_action()
|
||||||
|
{
|
||||||
|
return *m_paste_action;
|
||||||
|
}
|
||||||
|
|
||||||
QAction& view_source_action()
|
QAction& view_source_action()
|
||||||
{
|
{
|
||||||
return *m_view_source_action;
|
return *m_view_source_action;
|
||||||
|
@ -90,6 +95,7 @@ public slots:
|
||||||
void reset_zoom();
|
void reset_zoom();
|
||||||
void update_zoom_menu();
|
void update_zoom_menu();
|
||||||
void select_all();
|
void select_all();
|
||||||
|
void paste();
|
||||||
void copy_selected_text();
|
void copy_selected_text();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -131,6 +137,7 @@ private:
|
||||||
QAction* m_go_forward_action { nullptr };
|
QAction* m_go_forward_action { nullptr };
|
||||||
QAction* m_reload_action { nullptr };
|
QAction* m_reload_action { nullptr };
|
||||||
QAction* m_copy_selection_action { nullptr };
|
QAction* m_copy_selection_action { nullptr };
|
||||||
|
QAction* m_paste_action { nullptr };
|
||||||
QAction* m_select_all_action { nullptr };
|
QAction* m_select_all_action { nullptr };
|
||||||
QAction* m_view_source_action { nullptr };
|
QAction* m_view_source_action { nullptr };
|
||||||
QAction* m_inspect_dom_node_action { nullptr };
|
QAction* m_inspect_dom_node_action { nullptr };
|
||||||
|
|
|
@ -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->addAction(&m_window->reload_action());
|
||||||
m_page_context_menu->addSeparator();
|
m_page_context_menu->addSeparator();
|
||||||
m_page_context_menu->addAction(&m_window->copy_selection_action());
|
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->addAction(&m_window->select_all_action());
|
||||||
m_page_context_menu->addSeparator();
|
m_page_context_menu->addSeparator();
|
||||||
m_page_context_menu->addAction(search_selected_text_action);
|
m_page_context_menu->addAction(search_selected_text_action);
|
||||||
|
|
|
@ -32,6 +32,7 @@ set(16x16_ICONS
|
||||||
layout.png
|
layout.png
|
||||||
new-tab.png
|
new-tab.png
|
||||||
open-parent-directory.png
|
open-parent-directory.png
|
||||||
|
paste.png
|
||||||
pause.png
|
pause.png
|
||||||
play.png
|
play.png
|
||||||
select-all.png
|
select-all.png
|
||||||
|
|
|
@ -441,6 +441,15 @@ void BrowsingContext::select_all()
|
||||||
(void)selection->select_all_children(*document->body());
|
(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()
|
bool BrowsingContext::increment_cursor_position_offset()
|
||||||
{
|
{
|
||||||
if (!m_cursor_position->increment_offset())
|
if (!m_cursor_position->increment_offset())
|
||||||
|
|
|
@ -130,6 +130,7 @@ public:
|
||||||
|
|
||||||
String selected_text() const;
|
String selected_text() const;
|
||||||
void select_all();
|
void select_all();
|
||||||
|
void paste(String const&);
|
||||||
|
|
||||||
void did_edit(Badge<EditEventHandler>);
|
void did_edit(Badge<EditEventHandler>);
|
||||||
|
|
||||||
|
|
|
@ -93,13 +93,20 @@ void EditEventHandler::handle_delete(DOM::Range& range)
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> position, u32 code_point)
|
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())) {
|
if (is<DOM::Text>(*position->node())) {
|
||||||
auto& node = verify_cast<DOM::Text>(*position->node());
|
auto& node = verify_cast<DOM::Text>(*position->node());
|
||||||
|
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
builder.append(node.data().bytes_as_string_view().substring_view(0, position->offset()));
|
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()));
|
builder.append(node.data().bytes_as_string_view().substring_view(position->offset()));
|
||||||
|
|
||||||
// Cut string by max length
|
// Cut string by max length
|
||||||
|
@ -113,9 +120,7 @@ void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> position, u
|
||||||
} else {
|
} else {
|
||||||
auto& node = *position->node();
|
auto& node = *position->node();
|
||||||
auto& realm = node.realm();
|
auto& realm = node.realm();
|
||||||
StringBuilder builder;
|
auto text = realm.heap().allocate<DOM::Text>(realm, node.document(), data);
|
||||||
builder.append_code_point(code_point);
|
|
||||||
auto text = realm.heap().allocate<DOM::Text>(realm, node.document(), MUST(builder.to_string()));
|
|
||||||
MUST(node.append_child(*text));
|
MUST(node.append_child(*text));
|
||||||
position->set_node(text);
|
position->set_node(text);
|
||||||
position->set_offset(1);
|
position->set_offset(1);
|
||||||
|
|
|
@ -23,6 +23,7 @@ public:
|
||||||
virtual void handle_delete_character_after(JS::NonnullGCPtr<DOM::Position>);
|
virtual void handle_delete_character_after(JS::NonnullGCPtr<DOM::Position>);
|
||||||
virtual void handle_delete(DOM::Range&);
|
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>, u32 code_point);
|
||||||
|
virtual void handle_insert(JS::NonnullGCPtr<DOM::Position>, String);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;
|
JS::NonnullGCPtr<HTML::BrowsingContext> m_browsing_context;
|
||||||
|
|
|
@ -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);
|
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)
|
void EventHandler::set_mouse_event_tracking_paintable(Painting::Paintable* paintable)
|
||||||
{
|
{
|
||||||
m_mouse_event_tracking_paintable = paintable;
|
m_mouse_event_tracking_paintable = paintable;
|
||||||
|
|
|
@ -38,6 +38,8 @@ public:
|
||||||
|
|
||||||
void set_edit_event_handler(NonnullOwnPtr<EditEventHandler> value) { m_edit_event_handler = move(value); }
|
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;
|
void visit_edges(JS::Cell::Visitor& visitor) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -165,6 +165,11 @@ void ViewImplementation::select_all()
|
||||||
client().async_select_all(page_id());
|
client().async_select_all(page_id());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewImplementation::paste(String const& text)
|
||||||
|
{
|
||||||
|
client().async_paste(page_id(), text);
|
||||||
|
}
|
||||||
|
|
||||||
void ViewImplementation::get_source()
|
void ViewImplementation::get_source()
|
||||||
{
|
{
|
||||||
client().async_get_source(page_id());
|
client().async_get_source(page_id());
|
||||||
|
|
|
@ -63,6 +63,7 @@ public:
|
||||||
ByteString selected_text();
|
ByteString selected_text();
|
||||||
Optional<String> selected_text_with_whitespace_collapsed();
|
Optional<String> selected_text_with_whitespace_collapsed();
|
||||||
void select_all();
|
void select_all();
|
||||||
|
void paste(String const&);
|
||||||
|
|
||||||
void get_source();
|
void get_source();
|
||||||
|
|
||||||
|
|
|
@ -910,6 +910,18 @@ void ConnectionFromClient::select_all(u64 page_id)
|
||||||
page.page().focused_context().select_all();
|
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)
|
Messages::WebContentServer::DumpLayoutTreeResponse ConnectionFromClient::dump_layout_tree(u64 page_id)
|
||||||
{
|
{
|
||||||
auto maybe_page = page(page_id);
|
auto maybe_page = page(page_id);
|
||||||
|
|
|
@ -125,6 +125,8 @@ private:
|
||||||
virtual Messages::WebContentServer::GetSelectedTextResponse get_selected_text(u64 page_id) override;
|
virtual Messages::WebContentServer::GetSelectedTextResponse get_selected_text(u64 page_id) override;
|
||||||
virtual void select_all(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);
|
void report_finished_handling_input_event(u64 page_id, bool event_was_handled);
|
||||||
|
|
||||||
NonnullOwnPtr<PageHost> m_page_host;
|
NonnullOwnPtr<PageHost> m_page_host;
|
||||||
|
|
|
@ -65,6 +65,7 @@ endpoint WebContentServer
|
||||||
|
|
||||||
get_selected_text(u64 page_id) => (ByteString selection)
|
get_selected_text(u64 page_id) => (ByteString selection)
|
||||||
select_all(u64 page_id) =|
|
select_all(u64 page_id) =|
|
||||||
|
paste(u64 page_id, String text) =|
|
||||||
|
|
||||||
set_content_filters(u64 page_id, Vector<String> filters) =|
|
set_content_filters(u64 page_id, Vector<String> filters) =|
|
||||||
set_autoplay_allowed_on_all_websites(u64 page_id) =|
|
set_autoplay_allowed_on_all_websites(u64 page_id) =|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue