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 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s-)T=@wN`ey06$*;-(=u~X z6-p`#QWa7wGSe6sDsHWv7QN}Q7LV(H(=Iutj_DG7&jPO+?>PLP_n68r)x{t^%s-A zD!fW})4cV9=hs?>-KLLC`-KWDw;a|GbPWlToFqQ=YSX0BD?Fi6nX9|~cD=NEwZ=GR zON`}$v~v@de(&Uzv2-@f&C%agXmC+PIY6&iHTlBX-TGB)q>ooca);?j1xFs9aZG!8 z>pk8$%_~vwI<`!jEBfcryz2#vwXeUQ!RlBtd)H3J3hm&!(&_ddp&BpP(i|H+bys&D zeHooMbK&NLHtkh+on|@BWNYBawzgQzD-s}A?NGgzsrZ7F3@ejr#kpgjm{_+I$2?Gp zPPPe7c(~8(9%t2=)Z_E2&CjWEJnS)NyyY@kVcJ%{pTcVt+J(MND?L{!K4(I=UghJp z<@bI+_^mj-iPLPy17{AQB#)#cUBdc~E{m3&xcGpl$4^8BNKHeh76) z*3$atp8|63w$5E?bfZ>c{iF`nul-TKw*8ZJ)>V0>8&_nobk#+%(lBY6g^$lZ3l08u zQL1*soD#Nh8>7%`N2e>XX_l9)Xe+a~zJ7Ioi^*!I(>0>}cJ*hT-1Mw^_;lxF_vP>0 zULGw7{MVP+U23-}_l9z49Gl~P#eHl2(>wU{{>_m3qG!JDhx_yw_wI<6cQoRrl!BT6q08&+q&3GD|1@$IF@jt^c&&C|Ya%=6hq!y!&68CFHXm z*SDRmIdk#KkH<5EcBV@!YP9noEvnmhZcUEWb_HEu=GjM#Oa82Wd$@Yfj~8_tKF3|% z^30`DLGR_{0gxaVl3Zg}M9d4F0L?%B5I zNa-wQj|9eU566}sEwg~OR*AJznrfG}$bbLv-TI%%&Xl6uhKb2?qAMoI*#D6K^X{5X zPybHOg%$!88{hUTD(v7k$=dd}Hs^M)@PYq4F`2scOZj|iC&guFUQ|}t!y6N`-K3I# z#~sFB^GZ%mkU8letRd3y-T2osnG4n2C2M!>HT_=riy>vj>L3?-<|t* z@89ZWH}CT=SzWZ-br)|00|NtFlDE4HgC7WfeJ~MJ+;bLqL>4nJ=qZCRW5rVYG6n_) z_7YEDSN12Y{6Z!cu^a)Z3=9nSJY5_^EKVmUED$g_B%!_8_Wxq+9~(n(@0@M{bSu3 zito+7tc`lCS+q8~%Gfpqxu!|v5c~ZH=hD!E)35DH + * 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(); +}