From f7ae66cee395db5d8fd16398071bc2917124633e Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Fri, 15 May 2020 22:37:31 +0300 Subject: [PATCH] ClipboardHistory: Add a clipboard history applet :^) It will listen for clipboard content changes in the backgroud. Once you click on its icon, it will pop up a window listing all recorded clipboard contents. You can then double-click on an item to copy it again. --- Base/etc/SystemServer.ini | 5 + Base/res/icons/clipboard.png | Bin 0 -> 1813 bytes MenuApplets/CMakeLists.txt | 1 + MenuApplets/ClipboardHistory/CMakeLists.txt | 8 ++ .../ClipboardHistoryModel.cpp | 86 ++++++++++++++++++ .../ClipboardHistory/ClipboardHistoryModel.h | 57 ++++++++++++ MenuApplets/ClipboardHistory/IconWidget.cpp | 44 +++++++++ MenuApplets/ClipboardHistory/IconWidget.h | 42 +++++++++ MenuApplets/ClipboardHistory/main.cpp | 66 ++++++++++++++ 9 files changed, 309 insertions(+) create mode 100644 Base/res/icons/clipboard.png create mode 100644 MenuApplets/ClipboardHistory/CMakeLists.txt create mode 100644 MenuApplets/ClipboardHistory/ClipboardHistoryModel.cpp create mode 100644 MenuApplets/ClipboardHistory/ClipboardHistoryModel.h create mode 100644 MenuApplets/ClipboardHistory/IconWidget.cpp create mode 100644 MenuApplets/ClipboardHistory/IconWidget.h create mode 100644 MenuApplets/ClipboardHistory/main.cpp diff --git a/Base/etc/SystemServer.ini b/Base/etc/SystemServer.ini index 0e9727be55..2ef12ed8c3 100644 --- a/Base/etc/SystemServer.ini +++ b/Base/etc/SystemServer.ini @@ -81,6 +81,11 @@ Priority=low KeepAlive=1 User=anon +[ClipboardHistory.MenuApplet] +Priority=low +KeepAlive=1 +User=anon + [AudioServer] Socket=/tmp/portal/audio # TODO: we may want to start it lazily, but right now WindowServer connects to it immediately on startup diff --git a/Base/res/icons/clipboard.png b/Base/res/icons/clipboard.png new file mode 100644 index 0000000000000000000000000000000000000000..184c93c28568564311e7edb1a4233bbf22d6ed21 GIT binary patch literal 1813 zcmV+w2kQ8VP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3vbvg0ZYMgKL59s-D$7!Kx9)ib!m?+wOI;>0=e z%?V|L0SjqufkXAbzm5Lj$LuXx7sN;J6@E6_$O#Qm>o1PGR(9OygW4Jo{j?7-x(sTw zK87^sC-l493)>ocdF&^dBP`RX$nbK-Di zUM=&m=azM-Khu4O4WLvU7BbA$XrsnmRu9a2C0q`7ZCsy+I^mgVR9mb7@(|t*P~8K1 zfV{QnNPlk~;B%V&=D^ZVMrgbo(C#(-f%tA=vwOsk1Bq353TK1dinLbO=#upy>SsnN z+UN}WtpK|<<28>Ga5=QYC=o?eQW=sLmDPfhb<_-18fmMGPP*wj>Z~(dv|Kr$X3mhM z_lXG{IYTjRY%jWTFwqnvP%d*NW6;gJFM6yS$9h={RxTP-S;LsdE2o9t4PGhKTJDIn zlARU&;-1%Vqbt|%m<2&}o4UCHcq>zWb(cR#RVe5NW054@p|im}hkD&Y znnIZdfCy_lIHL^|P#$|gd#wU<&>9>C0ws9P#_|FMv~yhGBwJ%VQ()n~Hr@$(%w@-( zdpFJ|2;q!30op{9AeOZb`4g-lhZ6Rdb2j-uvMDA(w&)Hn`wJ2ohpQ zV#JCQFF{12q{z|W40O@Q5GBT#5>vuL3APgEC-f}5n8hw`@k>}_iA%~zpKP=i!JS{O;w-D1=aLajfYZw zUf5MbFNLs~(ET`ZT{9@gRzva76+l9B*G!4Fmb-FYGZh(|!ZDn5T^lO8D+UQ|)$E$y zwYw?zk#6MVBi;BX<)};d8_E%+d)DoRYNdbY@=$Eug`KG~*nJqUl87bsk6QY+{vAUt zB6!mK{D;_b ztvl@Zfqb6d^n(~5YeBDu&3w$!)cE6>Qn_avAt;9r#d3bW&a7-Yw;(M|1DnJ%bo{IB z!+X5==zg&CUe&bbM0p@C>62sgF-z!c$}^eh?IH7Cg_ZJzOdnG=s}D)a4XIrXCz~yO z!S?|#UE{l)TBK&;C8%di=_=GSrUWbbly}~C^vvovIOzhaqn&jZf8PlFm;fqw6tAnc` z2!4P#IXWr2NQwVT3N2zhIPS;0dyl(!fKV?p)$AArRLwHdiMW`}t%$)_1Q13K1~Dx$ zQ=d(xlJFc~_we!cF2=LG&;2>NmAuISpGZ8zbi*QEC!XB2bk6(45mu5E;&b8=gDyz? z$aUG}H_ke3JSuIyt^Pc>Lp}e-d z#C4hjNMHerNI`^*DmGAtg&3_GDJIgiANTP0JN^{8WO8kQkz*besE`~#_#gc4)+~&V zxk;fo5O}fek0Btq3p8rB{e5iPjT6BC3|wg~f29sgeUe^lY0)DfybWAjw={VVxZDBy zo^;8O9LY~pC=`JAGy0|+(0dDXt-8H6_i_3FWT>m;4RCM>3>PVT&Ewsj?Y;ebrrF;Q zq^okfMY;`G00006VoOIv08Ri-08aJbkqQ6+010qNS#tmYE+YT{E+YYWr9XB6000Mc zNliru7F;d}o3afqwkpSq0hf2Mx&T=tl6M%ec?WM8AiKiF#RaP&M7iMT zw*3tGN}LRv(|j2i7#L6tVIjr^6YqUTQIoI4i8~OHV*xn~uz3r^fD&~+H3kL-25T=z ztcvkQqj+dJV^C;00|NsCJl;um0U^aiB_wPCMN + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ClipboardHistoryModel.h" + +NonnullRefPtr ClipboardHistoryModel::create() +{ + return adopt(*new ClipboardHistoryModel()); +} + +ClipboardHistoryModel::~ClipboardHistoryModel() +{ +} + +String ClipboardHistoryModel::column_name(int column) const +{ + switch (column) { + case Column::Data: + return "Data"; + case Column::Type: + return "Type"; + default: + ASSERT_NOT_REACHED(); + } +} + +GUI::Model::ColumnMetadata ClipboardHistoryModel::column_metadata(int column) const +{ + switch (column) { + case Column::Data: + return { 200 }; + case Column::Type: + return { 100 }; + default: + ASSERT_NOT_REACHED(); + } +} + +GUI::Variant ClipboardHistoryModel::data(const GUI::ModelIndex& index, Role) const +{ + auto& data_and_type = m_history_items[index.row()]; + switch (index.column()) { + case Column::Data: + if (data_and_type.type.starts_with("text/")) + return data_and_type.data; + return "<...>"; + case Column::Type: + return data_and_type.type; + default: + ASSERT_NOT_REACHED(); + } +} + +void ClipboardHistoryModel::update() +{ + did_update(); +} + +void ClipboardHistoryModel::add_item(const GUI::Clipboard::DataAndType& item) +{ + m_history_items.prepend(item); + update(); +} diff --git a/MenuApplets/ClipboardHistory/ClipboardHistoryModel.h b/MenuApplets/ClipboardHistory/ClipboardHistoryModel.h new file mode 100644 index 0000000000..5f1357563f --- /dev/null +++ b/MenuApplets/ClipboardHistory/ClipboardHistoryModel.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019-2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include + +class ClipboardHistoryModel final : public GUI::Model { +public: + static NonnullRefPtr create(); + + enum Column { + Data, + Type, + __Count + }; + + 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); + +private: + virtual int row_count(const GUI::ModelIndex&) const override { return m_history_items.size(); } + virtual String column_name(int) const override; + virtual GUI::Model::ColumnMetadata column_metadata(int column) const override; + virtual int column_count(const GUI::ModelIndex&) const override { return Column::__Count; } + virtual GUI::Variant data(const GUI::ModelIndex&, Role = Role::Display) const override; + virtual void update() override; + + Vector m_history_items; +}; diff --git a/MenuApplets/ClipboardHistory/IconWidget.cpp b/MenuApplets/ClipboardHistory/IconWidget.cpp new file mode 100644 index 0000000000..bf95cf1f7d --- /dev/null +++ b/MenuApplets/ClipboardHistory/IconWidget.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "IconWidget.h" +#include + +IconWidget::IconWidget() +{ + set_icon(Gfx::Bitmap::load_from_file("/res/icons/clipboard.png")); + set_fill_with_background_color(true); +} + +IconWidget::~IconWidget() +{ +} + +void IconWidget::mousedown_event(GUI::MouseEvent&) +{ + if (on_click) + on_click(); +} diff --git a/MenuApplets/ClipboardHistory/IconWidget.h b/MenuApplets/ClipboardHistory/IconWidget.h new file mode 100644 index 0000000000..3b96c88f3d --- /dev/null +++ b/MenuApplets/ClipboardHistory/IconWidget.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include + +class IconWidget : public GUI::Label { + C_OBJECT(IconWidget) +public: + IconWidget(); + virtual ~IconWidget() override; + + Function on_click; + +private: + virtual void mousedown_event(GUI::MouseEvent&) override; +}; diff --git a/MenuApplets/ClipboardHistory/main.cpp b/MenuApplets/ClipboardHistory/main.cpp new file mode 100644 index 0000000000..39e961a351 --- /dev/null +++ b/MenuApplets/ClipboardHistory/main.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019-2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ClipboardHistoryModel.h" +#include "IconWidget.h" +#include +#include +#include + +int main(int argc, char* argv[]) +{ + GUI::Application app(argc, argv); + + auto main_window = GUI::Window::construct(); + main_window->set_title("Clipboard history"); + main_window->set_rect(670, 65, 325, 500); + + auto& table_view = main_window->set_main_widget(); + 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.type); + }; + + auto applet_window = GUI::Window::construct(); + applet_window->set_title("Clipboard history"); + applet_window->set_window_type(GUI::WindowType::MenuApplet); + auto& icon = applet_window->set_main_widget(); + icon.on_click = [&main_window = *main_window] { + main_window.show(); + }; + applet_window->resize(16, 16); + applet_window->show(); + + return app.exec(); +}