فهرست منبع

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.
TheFightingCatfish 4 سال پیش
والد
کامیت
0c53c2dfa2

+ 8 - 2
Userland/Applets/ClipboardHistory/ClipboardHistoryModel.h

@@ -10,7 +10,8 @@
 #include <LibGUI/Clipboard.h>
 #include <LibGUI/Model.h>
 
-class ClipboardHistoryModel final : public GUI::Model {
+class ClipboardHistoryModel final : public GUI::Model
+    , public GUI::Clipboard::ClipboardClient {
 public:
     static NonnullRefPtr<ClipboardHistoryModel> create();
 
@@ -24,16 +25,21 @@ public:
     virtual ~ClipboardHistoryModel() override;
 
     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);
 
 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 String column_name(int) const override;
     virtual int column_count(const GUI::ModelIndex&) const override { return Column::__Count; }
     virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const 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;
     size_t m_history_limit { 20 };
 };

+ 0 - 5
Userland/Applets/ClipboardHistory/main.cpp

@@ -49,11 +49,6 @@ int main(int argc, char* argv[])
     auto model = ClipboardHistoryModel::create();
     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) {
         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);

+ 49 - 38
Userland/Libraries/LibGUI/Clipboard.cpp

@@ -1,10 +1,10 @@
 /*
  * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, the SerenityOS developers.
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-#include <AK/Badge.h>
 #include <Clipboard/ClipboardClientEndpoint.h>
 #include <Clipboard/ClipboardServerEndpoint.h>
 #include <LibGUI/Clipboard.h>
@@ -23,18 +23,14 @@ private:
         : IPC::ServerConnection<ClipboardClientEndpoint, ClipboardServerEndpoint>(*this, "/tmp/portal/clipboard")
     {
     }
-    virtual void clipboard_data_changed(String const& mime_type) override;
-};
 
-Clipboard& Clipboard::the()
-{
-    static Clipboard* s_the;
-    if (!s_the)
-        s_the = new Clipboard;
-    return *s_the;
-}
+    virtual void clipboard_data_changed(String const& mime_type) override
+    {
+        Clipboard::the().clipboard_data_changed({}, mime_type);
+    }
+};
 
-ClipboardServerConnection* s_connection;
+static ClipboardServerConnection* s_connection;
 
 static ClipboardServerConnection& connection()
 {
@@ -46,8 +42,12 @@ void Clipboard::initialize(Badge<Application>)
     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
@@ -61,31 +61,6 @@ Clipboard::DataAndType Clipboard::data_and_type() const
     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
 {
     auto clipping = data_and_type();
@@ -126,7 +101,20 @@ RefPtr<Gfx::Bitmap> Clipboard::bitmap() const
     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;
     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);
 }
 
+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);
+}
+
 }

+ 28 - 16
Userland/Libraries/LibGUI/Clipboard.h

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, the SerenityOS developers.
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -15,22 +16,17 @@
 
 namespace GUI {
 
+class ClipboardServerConnection;
+
 class Clipboard {
 public:
-    static Clipboard& the();
-
-    ByteBuffer data() const { return data_and_type().data; }
-    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());
-    }
+    class ClipboardClient {
+    public:
+        ClipboardClient();
+        virtual ~ClipboardClient();
 
-    void set_bitmap(const Gfx::Bitmap&);
-    RefPtr<Gfx::Bitmap> bitmap() const;
+        virtual void clipboard_content_did_change(String const& mime_type) = 0;
+    };
 
     struct DataAndType {
         ByteBuffer data;
@@ -38,14 +34,30 @@ public:
         HashMap<String, String> metadata;
     };
 
+    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;
 
-    Function<void(const String& mime_type)> on_change;
+    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();
 
-    static void initialize(Badge<Application>);
+    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:
-    Clipboard();
+    Clipboard() = default;
+
+    HashTable<ClipboardClient*> m_clients;
 };
 
 }

+ 6 - 3
Userland/Libraries/LibGUI/TextEditor.cpp

@@ -83,6 +83,7 @@ void TextEditor::create_actions()
     m_cut_action->set_enabled(false);
     m_copy_action->set_enabled(false);
     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);
     if (is_multi_line()) {
         m_go_to_line_action = Action::create(
@@ -98,9 +99,6 @@ void TextEditor::create_actions()
             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)
@@ -1801,6 +1799,11 @@ void TextEditor::document_did_set_cursor(TextPosition const& 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)
 {
     if (m_document.ptr() == &document)

+ 7 - 1
Userland/Libraries/LibGUI/TextEditor.h

@@ -12,6 +12,8 @@
 #include <LibCore/ElapsedTimer.h>
 #include <LibCore/Timer.h>
 #include <LibGUI/AbstractScrollableWidget.h>
+#include <LibGUI/Action.h>
+#include <LibGUI/Clipboard.h>
 #include <LibGUI/Forward.h>
 #include <LibGUI/TextDocument.h>
 #include <LibGUI/TextRange.h>
@@ -24,7 +26,8 @@ namespace GUI {
 class TextEditor
     : public AbstractScrollableWidget
     , public TextDocument::Client
-    , public Syntax::HighlighterClient {
+    , public Syntax::HighlighterClient
+    , public Clipboard::ClipboardClient {
     C_OBJECT(TextEditor);
 
 public:
@@ -252,6 +255,9 @@ private:
     virtual GUI::TextDocument& highlighter_did_request_document() final { return document(); }
     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 paint_ruler(Painter&);
     void update_content_size();

+ 0 - 4
Userland/Libraries/LibVT/TerminalWidget.cpp

@@ -143,10 +143,6 @@ TerminalWidget::TerminalWidget(int ptm_fd, bool automatic_size_policy, RefPtr<Co
     m_context_menu->add_separator();
     m_context_menu->add_action(clear_including_history_action());
 
-    GUI::Clipboard::the().on_change = [this](const String&) {
-        update_paste_action();
-    };
-
     update_copy_action();
     update_paste_action();
 

+ 6 - 1
Userland/Libraries/LibVT/TerminalWidget.h

@@ -11,6 +11,7 @@
 #include <LibCore/ElapsedTimer.h>
 #include <LibCore/Notifier.h>
 #include <LibCore/Timer.h>
+#include <LibGUI/Clipboard.h>
 #include <LibGUI/Frame.h>
 #include <LibGfx/Bitmap.h>
 #include <LibGfx/Rect.h>
@@ -22,7 +23,8 @@ namespace VT {
 
 class TerminalWidget final
     : public GUI::Frame
-    , public VT::TerminalClient {
+    , public VT::TerminalClient
+    , public GUI::Clipboard::ClipboardClient {
     C_OBJECT(TerminalWidget);
 
 public:
@@ -123,6 +125,9 @@ private:
     virtual void emit(const u8*, size_t) 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 send_non_user_input(const ReadonlyBytes&);