mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 13:17:35 +00:00
LibGUI: Add a ClipboardClient for GUI::Clipboard
Anyone who inherits from `GUI::Clipboard::ClipboardClient` will receive clipboard notifications via `clipboard_content_did_change()`. Update ClipboardHistoryModel, TextEditor and TerminalWidget to inherit from this class.
This commit is contained in:
parent
95f393ebcd
commit
0c53c2dfa2
8 changed files with 106 additions and 72 deletions
|
@ -10,7 +10,8 @@
|
||||||
#include <LibGUI/Clipboard.h>
|
#include <LibGUI/Clipboard.h>
|
||||||
#include <LibGUI/Model.h>
|
#include <LibGUI/Model.h>
|
||||||
|
|
||||||
class ClipboardHistoryModel final : public GUI::Model {
|
class ClipboardHistoryModel final : public GUI::Model
|
||||||
|
, public GUI::Clipboard::ClipboardClient {
|
||||||
public:
|
public:
|
||||||
static NonnullRefPtr<ClipboardHistoryModel> create();
|
static NonnullRefPtr<ClipboardHistoryModel> create();
|
||||||
|
|
||||||
|
@ -24,16 +25,21 @@ public:
|
||||||
virtual ~ClipboardHistoryModel() override;
|
virtual ~ClipboardHistoryModel() override;
|
||||||
|
|
||||||
const GUI::Clipboard::DataAndType& item_at(int index) const { return m_history_items[index]; }
|
const GUI::Clipboard::DataAndType& item_at(int index) const { return m_history_items[index]; }
|
||||||
void add_item(const GUI::Clipboard::DataAndType& item);
|
|
||||||
void remove_item(int index);
|
void remove_item(int index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void add_item(const GUI::Clipboard::DataAndType& item);
|
||||||
|
|
||||||
|
// ^GUI::Model
|
||||||
virtual int row_count(const GUI::ModelIndex&) const override { return m_history_items.size(); }
|
virtual int row_count(const GUI::ModelIndex&) const override { return m_history_items.size(); }
|
||||||
virtual String column_name(int) const override;
|
virtual String column_name(int) const override;
|
||||||
virtual int column_count(const GUI::ModelIndex&) const override { return Column::__Count; }
|
virtual int column_count(const GUI::ModelIndex&) const override { return Column::__Count; }
|
||||||
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
||||||
virtual void update() override;
|
virtual void update() override;
|
||||||
|
|
||||||
|
// ^GUI::Clipboard::ClipboardClient
|
||||||
|
virtual void clipboard_content_did_change(const String&) override { add_item(GUI::Clipboard::the().data_and_type()); }
|
||||||
|
|
||||||
Vector<GUI::Clipboard::DataAndType> m_history_items;
|
Vector<GUI::Clipboard::DataAndType> m_history_items;
|
||||||
size_t m_history_limit { 20 };
|
size_t m_history_limit { 20 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,11 +49,6 @@ int main(int argc, char* argv[])
|
||||||
auto model = ClipboardHistoryModel::create();
|
auto model = ClipboardHistoryModel::create();
|
||||||
table_view.set_model(model);
|
table_view.set_model(model);
|
||||||
|
|
||||||
GUI::Clipboard::the().on_change = [&](const String&) {
|
|
||||||
auto item = GUI::Clipboard::the().data_and_type();
|
|
||||||
model->add_item(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
table_view.on_activation = [&](const GUI::ModelIndex& index) {
|
table_view.on_activation = [&](const GUI::ModelIndex& index) {
|
||||||
auto& data_and_type = model->item_at(index.row());
|
auto& data_and_type = model->item_at(index.row());
|
||||||
GUI::Clipboard::the().set_data(data_and_type.data, data_and_type.mime_type, data_and_type.metadata);
|
GUI::Clipboard::the().set_data(data_and_type.data, data_and_type.mime_type, data_and_type.metadata);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* Copyright (c) 2021, the SerenityOS developers.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/Badge.h>
|
|
||||||
#include <Clipboard/ClipboardClientEndpoint.h>
|
#include <Clipboard/ClipboardClientEndpoint.h>
|
||||||
#include <Clipboard/ClipboardServerEndpoint.h>
|
#include <Clipboard/ClipboardServerEndpoint.h>
|
||||||
#include <LibGUI/Clipboard.h>
|
#include <LibGUI/Clipboard.h>
|
||||||
|
@ -23,18 +23,14 @@ private:
|
||||||
: IPC::ServerConnection<ClipboardClientEndpoint, ClipboardServerEndpoint>(*this, "/tmp/portal/clipboard")
|
: IPC::ServerConnection<ClipboardClientEndpoint, ClipboardServerEndpoint>(*this, "/tmp/portal/clipboard")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
virtual void clipboard_data_changed(String const& mime_type) override;
|
|
||||||
|
virtual void clipboard_data_changed(String const& mime_type) override
|
||||||
|
{
|
||||||
|
Clipboard::the().clipboard_data_changed({}, mime_type);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Clipboard& Clipboard::the()
|
static ClipboardServerConnection* s_connection;
|
||||||
{
|
|
||||||
static Clipboard* s_the;
|
|
||||||
if (!s_the)
|
|
||||||
s_the = new Clipboard;
|
|
||||||
return *s_the;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClipboardServerConnection* s_connection;
|
|
||||||
|
|
||||||
static ClipboardServerConnection& connection()
|
static ClipboardServerConnection& connection()
|
||||||
{
|
{
|
||||||
|
@ -46,8 +42,12 @@ void Clipboard::initialize(Badge<Application>)
|
||||||
s_connection = &ClipboardServerConnection::construct().leak_ref();
|
s_connection = &ClipboardServerConnection::construct().leak_ref();
|
||||||
}
|
}
|
||||||
|
|
||||||
Clipboard::Clipboard()
|
Clipboard& Clipboard::the()
|
||||||
{
|
{
|
||||||
|
static Clipboard* s_the;
|
||||||
|
if (!s_the)
|
||||||
|
s_the = new Clipboard;
|
||||||
|
return *s_the;
|
||||||
}
|
}
|
||||||
|
|
||||||
Clipboard::DataAndType Clipboard::data_and_type() const
|
Clipboard::DataAndType Clipboard::data_and_type() const
|
||||||
|
@ -61,31 +61,6 @@ Clipboard::DataAndType Clipboard::data_and_type() const
|
||||||
return { data, type, metadata };
|
return { data, type, metadata };
|
||||||
}
|
}
|
||||||
|
|
||||||
void Clipboard::set_data(ReadonlyBytes data, const String& type, const HashMap<String, String>& metadata)
|
|
||||||
{
|
|
||||||
auto buffer = Core::AnonymousBuffer::create_with_size(data.size());
|
|
||||||
if (!buffer.is_valid()) {
|
|
||||||
dbgln("GUI::Clipboard::set_data() failed to create a buffer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!data.is_empty())
|
|
||||||
memcpy(buffer.data<void>(), data.data(), data.size());
|
|
||||||
|
|
||||||
connection().async_set_clipboard_data(move(buffer), type, metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Clipboard::clear()
|
|
||||||
{
|
|
||||||
connection().async_set_clipboard_data({}, {}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClipboardServerConnection::clipboard_data_changed(String const& mime_type)
|
|
||||||
{
|
|
||||||
auto& clipboard = Clipboard::the();
|
|
||||||
if (clipboard.on_change)
|
|
||||||
clipboard.on_change(mime_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
RefPtr<Gfx::Bitmap> Clipboard::bitmap() const
|
RefPtr<Gfx::Bitmap> Clipboard::bitmap() const
|
||||||
{
|
{
|
||||||
auto clipping = data_and_type();
|
auto clipping = data_and_type();
|
||||||
|
@ -126,7 +101,20 @@ RefPtr<Gfx::Bitmap> Clipboard::bitmap() const
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Clipboard::set_bitmap(const Gfx::Bitmap& bitmap)
|
void Clipboard::set_data(ReadonlyBytes const& data, String const& type, HashMap<String, String> const& metadata)
|
||||||
|
{
|
||||||
|
auto buffer = Core::AnonymousBuffer::create_with_size(data.size());
|
||||||
|
if (!buffer.is_valid()) {
|
||||||
|
dbgln("GUI::Clipboard::set_data() failed to create a buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!data.is_empty())
|
||||||
|
memcpy(buffer.data<void>(), data.data(), data.size());
|
||||||
|
|
||||||
|
connection().async_set_clipboard_data(move(buffer), type, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clipboard::set_bitmap(Gfx::Bitmap const& bitmap)
|
||||||
{
|
{
|
||||||
HashMap<String, String> metadata;
|
HashMap<String, String> metadata;
|
||||||
metadata.set("width", String::number(bitmap.width()));
|
metadata.set("width", String::number(bitmap.width()));
|
||||||
|
@ -137,4 +125,27 @@ void Clipboard::set_bitmap(const Gfx::Bitmap& bitmap)
|
||||||
set_data({ bitmap.scanline(0), bitmap.size_in_bytes() }, "image/x-serenityos", metadata);
|
set_data({ bitmap.scanline(0), bitmap.size_in_bytes() }, "image/x-serenityos", metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Clipboard::clear()
|
||||||
|
{
|
||||||
|
connection().async_set_clipboard_data({}, {}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clipboard::clipboard_data_changed(Badge<ClipboardServerConnection>, String const& mime_type)
|
||||||
|
{
|
||||||
|
if (on_change)
|
||||||
|
on_change(mime_type);
|
||||||
|
for (auto* client : m_clients)
|
||||||
|
client->clipboard_content_did_change(mime_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
Clipboard::ClipboardClient::ClipboardClient()
|
||||||
|
{
|
||||||
|
Clipboard::the().register_client({}, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Clipboard::ClipboardClient::~ClipboardClient()
|
||||||
|
{
|
||||||
|
Clipboard::the().unregister_client({}, *this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* Copyright (c) 2021, the SerenityOS developers.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -15,22 +16,17 @@
|
||||||
|
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
|
class ClipboardServerConnection;
|
||||||
|
|
||||||
class Clipboard {
|
class Clipboard {
|
||||||
public:
|
public:
|
||||||
static Clipboard& the();
|
class ClipboardClient {
|
||||||
|
public:
|
||||||
|
ClipboardClient();
|
||||||
|
virtual ~ClipboardClient();
|
||||||
|
|
||||||
ByteBuffer data() const { return data_and_type().data; }
|
virtual void clipboard_content_did_change(String const& mime_type) = 0;
|
||||||
String mime_type() const { return data_and_type().mime_type; }
|
};
|
||||||
void set_data(ReadonlyBytes, const String& mime_type = "text/plain", const HashMap<String, String>& metadata = {});
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
void set_plain_text(const String& text)
|
|
||||||
{
|
|
||||||
set_data(text.bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_bitmap(const Gfx::Bitmap&);
|
|
||||||
RefPtr<Gfx::Bitmap> bitmap() const;
|
|
||||||
|
|
||||||
struct DataAndType {
|
struct DataAndType {
|
||||||
ByteBuffer data;
|
ByteBuffer data;
|
||||||
|
@ -38,14 +34,30 @@ public:
|
||||||
HashMap<String, String> metadata;
|
HashMap<String, String> metadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
DataAndType data_and_type() const;
|
|
||||||
|
|
||||||
Function<void(const String& mime_type)> on_change;
|
|
||||||
|
|
||||||
static void initialize(Badge<Application>);
|
static void initialize(Badge<Application>);
|
||||||
|
static Clipboard& the();
|
||||||
|
|
||||||
|
DataAndType data_and_type() const;
|
||||||
|
ByteBuffer data() const { return data_and_type().data; }
|
||||||
|
String mime_type() const { return data_and_type().mime_type; }
|
||||||
|
RefPtr<Gfx::Bitmap> bitmap() const;
|
||||||
|
|
||||||
|
void set_data(ReadonlyBytes const& data, String const& mime_type = "text/plain", HashMap<String, String> const& metadata = {});
|
||||||
|
void set_plain_text(String const& text) { set_data(text.bytes()); }
|
||||||
|
void set_bitmap(Gfx::Bitmap const&);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
void clipboard_data_changed(Badge<ClipboardServerConnection>, String const& mime_type);
|
||||||
|
|
||||||
|
void register_client(Badge<ClipboardClient>, ClipboardClient& client) { m_clients.set(&client); }
|
||||||
|
void unregister_client(Badge<ClipboardClient>, ClipboardClient& client) { m_clients.remove(&client); }
|
||||||
|
|
||||||
|
Function<void(String const& mime_type)> on_change;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Clipboard();
|
Clipboard() = default;
|
||||||
|
|
||||||
|
HashTable<ClipboardClient*> m_clients;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ void TextEditor::create_actions()
|
||||||
m_cut_action->set_enabled(false);
|
m_cut_action->set_enabled(false);
|
||||||
m_copy_action->set_enabled(false);
|
m_copy_action->set_enabled(false);
|
||||||
m_paste_action = CommonActions::make_paste_action([&](auto&) { paste(); }, this);
|
m_paste_action = CommonActions::make_paste_action([&](auto&) { paste(); }, this);
|
||||||
|
m_paste_action->set_enabled(is_editable() && Clipboard::the().mime_type().starts_with("text/") && !Clipboard::the().data().is_empty());
|
||||||
m_delete_action = CommonActions::make_delete_action([&](auto&) { do_delete(); }, this);
|
m_delete_action = CommonActions::make_delete_action([&](auto&) { do_delete(); }, this);
|
||||||
if (is_multi_line()) {
|
if (is_multi_line()) {
|
||||||
m_go_to_line_action = Action::create(
|
m_go_to_line_action = Action::create(
|
||||||
|
@ -98,9 +99,6 @@ void TextEditor::create_actions()
|
||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
m_select_all_action = CommonActions::make_select_all_action([this](auto&) { select_all(); }, this);
|
m_select_all_action = CommonActions::make_select_all_action([this](auto&) { select_all(); }, this);
|
||||||
Clipboard::the().on_change = [this](auto const& mime_type) {
|
|
||||||
m_paste_action->set_enabled(is_editable() && mime_type.starts_with("text/") && !Clipboard::the().data().is_empty());
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextEditor::set_text(StringView const& text)
|
void TextEditor::set_text(StringView const& text)
|
||||||
|
@ -1801,6 +1799,11 @@ void TextEditor::document_did_set_cursor(TextPosition const& position)
|
||||||
set_cursor(position);
|
set_cursor(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextEditor::clipboard_content_did_change(String const& mime_type)
|
||||||
|
{
|
||||||
|
m_paste_action->set_enabled(is_editable() && mime_type.starts_with("text/") && !Clipboard::the().data().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
void TextEditor::set_document(TextDocument& document)
|
void TextEditor::set_document(TextDocument& document)
|
||||||
{
|
{
|
||||||
if (m_document.ptr() == &document)
|
if (m_document.ptr() == &document)
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include <LibCore/ElapsedTimer.h>
|
#include <LibCore/ElapsedTimer.h>
|
||||||
#include <LibCore/Timer.h>
|
#include <LibCore/Timer.h>
|
||||||
#include <LibGUI/AbstractScrollableWidget.h>
|
#include <LibGUI/AbstractScrollableWidget.h>
|
||||||
|
#include <LibGUI/Action.h>
|
||||||
|
#include <LibGUI/Clipboard.h>
|
||||||
#include <LibGUI/Forward.h>
|
#include <LibGUI/Forward.h>
|
||||||
#include <LibGUI/TextDocument.h>
|
#include <LibGUI/TextDocument.h>
|
||||||
#include <LibGUI/TextRange.h>
|
#include <LibGUI/TextRange.h>
|
||||||
|
@ -24,7 +26,8 @@ namespace GUI {
|
||||||
class TextEditor
|
class TextEditor
|
||||||
: public AbstractScrollableWidget
|
: public AbstractScrollableWidget
|
||||||
, public TextDocument::Client
|
, public TextDocument::Client
|
||||||
, public Syntax::HighlighterClient {
|
, public Syntax::HighlighterClient
|
||||||
|
, public Clipboard::ClipboardClient {
|
||||||
C_OBJECT(TextEditor);
|
C_OBJECT(TextEditor);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -252,6 +255,9 @@ private:
|
||||||
virtual GUI::TextDocument& highlighter_did_request_document() final { return document(); }
|
virtual GUI::TextDocument& highlighter_did_request_document() final { return document(); }
|
||||||
virtual GUI::TextPosition highlighter_did_request_cursor() const final { return m_cursor; }
|
virtual GUI::TextPosition highlighter_did_request_cursor() const final { return m_cursor; }
|
||||||
|
|
||||||
|
// ^Clipboard::ClipboardClient
|
||||||
|
virtual void clipboard_content_did_change(String const& mime_type) override;
|
||||||
|
|
||||||
void create_actions();
|
void create_actions();
|
||||||
void paint_ruler(Painter&);
|
void paint_ruler(Painter&);
|
||||||
void update_content_size();
|
void update_content_size();
|
||||||
|
|
|
@ -143,10 +143,6 @@ TerminalWidget::TerminalWidget(int ptm_fd, bool automatic_size_policy, RefPtr<Co
|
||||||
m_context_menu->add_separator();
|
m_context_menu->add_separator();
|
||||||
m_context_menu->add_action(clear_including_history_action());
|
m_context_menu->add_action(clear_including_history_action());
|
||||||
|
|
||||||
GUI::Clipboard::the().on_change = [this](const String&) {
|
|
||||||
update_paste_action();
|
|
||||||
};
|
|
||||||
|
|
||||||
update_copy_action();
|
update_copy_action();
|
||||||
update_paste_action();
|
update_paste_action();
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <LibCore/ElapsedTimer.h>
|
#include <LibCore/ElapsedTimer.h>
|
||||||
#include <LibCore/Notifier.h>
|
#include <LibCore/Notifier.h>
|
||||||
#include <LibCore/Timer.h>
|
#include <LibCore/Timer.h>
|
||||||
|
#include <LibGUI/Clipboard.h>
|
||||||
#include <LibGUI/Frame.h>
|
#include <LibGUI/Frame.h>
|
||||||
#include <LibGfx/Bitmap.h>
|
#include <LibGfx/Bitmap.h>
|
||||||
#include <LibGfx/Rect.h>
|
#include <LibGfx/Rect.h>
|
||||||
|
@ -22,7 +23,8 @@ namespace VT {
|
||||||
|
|
||||||
class TerminalWidget final
|
class TerminalWidget final
|
||||||
: public GUI::Frame
|
: public GUI::Frame
|
||||||
, public VT::TerminalClient {
|
, public VT::TerminalClient
|
||||||
|
, public GUI::Clipboard::ClipboardClient {
|
||||||
C_OBJECT(TerminalWidget);
|
C_OBJECT(TerminalWidget);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -123,6 +125,9 @@ private:
|
||||||
virtual void emit(const u8*, size_t) override;
|
virtual void emit(const u8*, size_t) override;
|
||||||
virtual void set_cursor_style(CursorStyle) override;
|
virtual void set_cursor_style(CursorStyle) override;
|
||||||
|
|
||||||
|
// ^GUI::Clipboard::ClipboardClient
|
||||||
|
virtual void clipboard_content_did_change(const String&) override { update_paste_action(); }
|
||||||
|
|
||||||
void set_logical_focus(bool);
|
void set_logical_focus(bool);
|
||||||
|
|
||||||
void send_non_user_input(const ReadonlyBytes&);
|
void send_non_user_input(const ReadonlyBytes&);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue