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: https://github.com/SerenityOS/serenity/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);
|
||||
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);
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -130,6 +130,7 @@ public:
|
|||
|
||||
String selected_text() const;
|
||||
void select_all();
|
||||
void paste(String const&);
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) =|
|
||||
|
|
Loading…
Add table
Reference in a new issue