[graphics/drawy] src: Add custom element widget

Laurent Montel <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 9577867c0da40e0db718398ae2fa52fa1dd1c951 by Laurent Montel.
Committed on 21/07/2026 at 05:49.
Pushed by mlaurent into branch 'master'.

Add custom element widget

M  +2    -0    src/gui/CMakeLists.txt
M  +14   -6    src/gui/customelements/customelementsmanager.cpp
M  +4    -2    src/gui/customelements/customelementsmanager.hpp
A  +62   -0    src/gui/customelements/customelementsmodel.cpp     [License: GPL(v3.0+)]
A  +37   -0    src/gui/customelements/customelementsmodel.hpp     [License: GPL(v3.0+)]
M  +2    -0    src/widgets/CMakeLists.txt
M  +1    -0    src/widgets/autotests/CMakeLists.txt
A  +24   -0    src/widgets/autotests/customelementswidgettest.cpp     [License: GPL(v3.0+)]
A  +19   -0    src/widgets/autotests/customelementswidgettest.hpp     [License: GPL(v3.0+)]
M  +9    -0    src/widgets/context/uicontext.cpp
M  +3    -0    src/widgets/context/uicontext.hpp
A  +27   -0    src/widgets/customelementswidget/customelementswidget.cpp     [License: GPL(v3.0+)]
A  +20   -0    src/widgets/customelementswidget/customelementswidget.hpp     [License: GPL(v3.0+)]
M  +13   -0    src/widgets/keybindings/actionmanager.cpp
M  +2    -0    src/widgets/keybindings/actionmanager.hpp

https://invent.kde.org/graphics/drawy/-/commit/9577867c0da40e0db718398ae2fa52fa1dd1c951

diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 57cbfd82..6cd11cf3 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -146,6 +146,8 @@ target_sources(
         customelements/customelement.cpp
         customelements/customelementsutils.hpp
         customelements/customelementsutils.cpp
+        customelements/customelementsmodel.hpp
+        customelements/customelementsmodel.cpp
 )
 
 ecm_qt_declare_logging_category(libdrawygui HEADER drawy_gui_debug.h IDENTIFIER DRAWY_GUI_LOG
diff --git a/src/gui/customelements/customelementsmanager.cpp b/src/gui/customelements/customelementsmanager.cpp
index be6dde1f..0b05af7e 100644
--- a/src/gui/customelements/customelementsmanager.cpp
+++ b/src/gui/customelements/customelementsmanager.cpp
@@ -5,6 +5,7 @@
  */
 
 #include "customelementsmanager.hpp"
+#include "customelements/customelementsmodel.hpp"
 #include "customelements/customelementsutils.hpp"
 #include "drawy_gui_debug.h"
 #include "item/item.hpp"
@@ -15,9 +16,15 @@
 using namespace Qt::Literals::StringLiterals;
 CustomElementsManager::CustomElementsManager(QObject *parent)
     : QObject{parent}
+    , mCustomElementsModel(new CustomElementsModel(this))
 {
 }
 
+CustomElementsModel *CustomElementsManager::customElementsModel() const
+{
+    return mCustomElementsModel;
+}
+
 CustomElementsManager::~CustomElementsManager() = default;
 
 CustomElementsManager *CustomElementsManager::self()
@@ -42,15 +49,16 @@ void CustomElementsManager::loadCustomElements()
             qCWarning(DRAWY_GUI_LOG) << "Invalid version" << version;
             return;
         }
-        mCustomElements.clear();
+        QList<CustomElement> customElements;
         const QJsonArray array = obj["librairies"_L1].toArray();
         for (const auto &r : array) {
             CustomElement e;
             e.load(r.toObject());
             if (e.isValid()) {
-                mCustomElements.append(e);
+                customElements.append(e);
             }
         }
+        mCustomElementsModel->setCustomElements(customElements);
     }
 }
 
@@ -59,7 +67,7 @@ void CustomElementsManager::saveCustomElements(const QString &fileName)
     QJsonObject obj;
     obj["version"_L1] = QString::number(1);
     QJsonArray elementsObj;
-    for (const auto &element : std::as_const(mCustomElements)) {
+    for (const auto &element : mCustomElementsModel->customElements()) {
         elementsObj.append(element.save());
     }
     obj["librairies"_L1] = elementsObj;
@@ -78,7 +86,7 @@ void CustomElementsManager::saveCustomElements(const QString &fileName)
 
 void CustomElementsManager::exportToFile(const QString &fileName)
 {
-    if (mCustomElements.isEmpty()) {
+    if (mCustomElementsModel->customElements().isEmpty()) {
         qCWarning(DRAWY_GUI_LOG) << "Custom Elements list is empty";
         return;
     }
@@ -89,13 +97,13 @@ void CustomElementsManager::addItem(const std::shared_ptr<Item> &item)
 {
     CustomElement element;
     element.setItem(item);
-    mCustomElements.append(element);
+    mCustomElementsModel->addCustomElement(element);
     saveCustomElements();
 }
 
 bool CustomElementsManager::isEmpty() const
 {
-    return mCustomElements.isEmpty();
+    return mCustomElementsModel->isEmpty();
 }
 
 #include "moc_customelementsmanager.cpp"
diff --git a/src/gui/customelements/customelementsmanager.hpp b/src/gui/customelements/customelementsmanager.hpp
index ab0315d7..758900a2 100644
--- a/src/gui/customelements/customelementsmanager.hpp
+++ b/src/gui/customelements/customelementsmanager.hpp
@@ -5,13 +5,13 @@
  */
 
 #pragma once
-#include "customelements/customelement.hpp"
 #include "libdrawygui_export.h"
 #include <QList>
 #include <QObject>
 #include <memory>
 
 class Item;
+class CustomElementsModel;
 class LIBDRAWYGUI_EXPORT CustomElementsManager : public QObject
 {
     Q_OBJECT
@@ -27,7 +27,9 @@ public:
     void exportToFile(const QString &fileName);
     void addItem(const std::shared_ptr<Item> &item);
 
+    [[nodiscard]] CustomElementsModel *customElementsModel() const;
+
 private:
     explicit CustomElementsManager(QObject *parent = nullptr);
-    QList<CustomElement> mCustomElements;
+    CustomElementsModel *const mCustomElementsModel;
 };
diff --git a/src/gui/customelements/customelementsmodel.cpp b/src/gui/customelements/customelementsmodel.cpp
new file mode 100644
index 00000000..6373da88
--- /dev/null
+++ b/src/gui/customelements/customelementsmodel.cpp
@@ -0,0 +1,62 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Laurent Montel <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "customelementsmodel.hpp"
+
+CustomElementsModel::CustomElementsModel(QObject *parent)
+    : QAbstractListModel{parent}
+{
+}
+
+CustomElementsModel::~CustomElementsModel() = default;
+
+int CustomElementsModel::rowCount(const QModelIndex &parent) const
+{
+    if (parent.isValid()) { // flat model
+        return 0;
+    }
+    return mCustomElements.count();
+}
+
+QVariant CustomElementsModel::data([[maybe_unused]] const QModelIndex &index, [[maybe_unused]] int role) const
+{
+    if (index.row() < 0 || index.row() >= mCustomElements.count()) {
+        return {};
+    }
+    const auto customElement = mCustomElements.at(index.row());
+    switch (role) {
+    case TimeStamp:
+        return customElement.timeStamp();
+    }
+    return {};
+}
+
+QList<CustomElement> CustomElementsModel::customElements() const
+{
+    return mCustomElements;
+}
+
+void CustomElementsModel::addCustomElement(const CustomElement &element)
+{
+    const int numberOfElement = mCustomElements.count();
+    beginInsertRows(QModelIndex(), numberOfElement, numberOfElement);
+    mCustomElements.append(element);
+    endInsertRows();
+}
+
+void CustomElementsModel::setCustomElements(const QList<CustomElement> &newCustomElements)
+{
+    beginResetModel();
+    mCustomElements = newCustomElements;
+    endResetModel();
+}
+
+bool CustomElementsModel::isEmpty() const
+{
+    return mCustomElements.isEmpty();
+}
+
+#include "moc_customelementsmodel.cpp"
diff --git a/src/gui/customelements/customelementsmodel.hpp b/src/gui/customelements/customelementsmodel.hpp
new file mode 100644
index 00000000..a5dc041d
--- /dev/null
+++ b/src/gui/customelements/customelementsmodel.hpp
@@ -0,0 +1,37 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Laurent Montel <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+#include "customelement.hpp"
+#include "libdrawygui_export.h"
+#include <QAbstractListModel>
+#include <QList>
+
+class LIBDRAWYGUI_EXPORT CustomElementsModel : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    enum CustomElementRoles {
+        TimeStamp = Qt::UserRole + 1,
+    };
+    Q_ENUM(CustomElementRoles)
+
+    explicit CustomElementsModel(QObject *parent = nullptr);
+    ~CustomElementsModel() override;
+
+    [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
+
+    [[nodiscard]] QList<CustomElement> customElements() const;
+    void setCustomElements(const QList<CustomElement> &newCustomElements);
+
+    [[nodiscard]] bool isEmpty() const;
+
+    void addCustomElement(const CustomElement &element);
+
+private:
+    QList<CustomElement> mCustomElements;
+};
diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt
index 9734040f..1626bbcf 100644
--- a/src/widgets/CMakeLists.txt
+++ b/src/widgets/CMakeLists.txt
@@ -252,6 +252,8 @@ target_sources(
         libdrawywidgets_private_export.h
         common/renderitems.cpp
         common/renderitems.hpp
+        customelementswidget/customelementswidget.hpp
+        customelementswidget/customelementswidget.cpp
 )
 
 if(USE_DBUS)
diff --git a/src/widgets/autotests/CMakeLists.txt b/src/widgets/autotests/CMakeLists.txt
index cda94434..0fe04a0b 100644
--- a/src/widgets/autotests/CMakeLists.txt
+++ b/src/widgets/autotests/CMakeLists.txt
@@ -77,3 +77,4 @@ set_tests_properties(
 )
 
 add_drawy_unittest(cornerrectangletypewidgettest.cpp)
+add_drawy_unittest(customelementswidgettest.cpp)
diff --git a/src/widgets/autotests/customelementswidgettest.cpp b/src/widgets/autotests/customelementswidgettest.cpp
new file mode 100644
index 00000000..eb8f730e
--- /dev/null
+++ b/src/widgets/autotests/customelementswidgettest.cpp
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Laurent Montel <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+#include "customelementswidgettest.hpp"
+#include "customelementswidget/customelementswidget.hpp"
+#include <QTest>
+#include <QVBoxLayout>
+QTEST_MAIN(CustomElementsWidgetTest)
+using namespace Qt::Literals::StringLiterals;
+CustomElementsWidgetTest::CustomElementsWidgetTest(QObject *parent)
+    : QObject{parent}
+{
+}
+
+void CustomElementsWidgetTest::shouldHaveDefaultValues()
+{
+    const CustomElementsWidget w(nullptr);
+
+    auto mainLayout = w.findChild<QVBoxLayout *>(u"mainLayout"_s);
+    QVERIFY(mainLayout);
+}
+#include "moc_customelementswidgettest.cpp"
diff --git a/src/widgets/autotests/customelementswidgettest.hpp b/src/widgets/autotests/customelementswidgettest.hpp
new file mode 100644
index 00000000..3d096fab
--- /dev/null
+++ b/src/widgets/autotests/customelementswidgettest.hpp
@@ -0,0 +1,19 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Laurent Montel <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+#pragma once
+
+#include <QObject>
+
+class CustomElementsWidgetTest : public QObject
+{
+    Q_OBJECT
+public:
+    explicit CustomElementsWidgetTest(QObject *parent = nullptr);
+    ~CustomElementsWidgetTest() override = default;
+
+private Q_SLOTS:
+    void shouldHaveDefaultValues();
+};
diff --git a/src/widgets/context/uicontext.cpp b/src/widgets/context/uicontext.cpp
index b6599cc5..75f88ff4 100644
--- a/src/widgets/context/uicontext.cpp
+++ b/src/widgets/context/uicontext.cpp
@@ -14,6 +14,7 @@
 #include "components/toolbar.hpp"
 #include "components/topleftwidgets.hpp"
 #include "components/topwidgets.hpp"
+#include "customelements/customelementsmanager.hpp"
 #include "data-structures/quadtree.hpp"
 #include "drawy_debug.h"
 #include "drawyglobalconfig.h"
@@ -47,6 +48,7 @@ UIContext::UIContext(ApplicationContext *context)
     , m_keybindManager(new KeybindManager(this))
     , m_event(new Event())
     , mPluginFormManager(PluginFormManager::self())
+    , mCustomElementsManager(CustomElementsManager::self())
     , m_applicationContext{context}
 {
 }
@@ -126,6 +128,11 @@ void UIContext::slotThemeChanged()
     Q_EMIT themeChanged(isDark);
 }
 
+CustomElementsManager *UIContext::customElementsManager() const
+{
+    return mCustomElementsManager;
+}
+
 ToolBar *UIContext::toolBar() const
 {
     return m_toolBar;
@@ -282,6 +289,8 @@ void UIContext::showContextMenu() const
         } else {
             menu->addAction(actionManager()->action(ActionManager::Action::LockItems));
         }
+        menu->addSeparator();
+        menu->addAction(actionManager()->action(ActionManager::Action::AddCustomElement));
     }
 
     menu->addSeparator();
diff --git a/src/widgets/context/uicontext.hpp b/src/widgets/context/uicontext.hpp
index 15abf683..07481fde 100644
--- a/src/widgets/context/uicontext.hpp
+++ b/src/widgets/context/uicontext.hpp
@@ -17,6 +17,7 @@ class TopWidgets;
 class TopLeftWidgets;
 class BottomLeftWidgets;
 class PluginFormManager;
+class CustomElementsManager;
 
 class UIContext : public QObject
 {
@@ -36,6 +37,7 @@ public:
     [[nodiscard]] KeybindManager *keybindManager() const;
     [[nodiscard]] ActionManager *actionManager() const;
     [[nodiscard]] PropertyManager *propertyManager() const;
+    [[nodiscard]] CustomElementsManager *customElementsManager() const;
 
     void showContextMenu() const;
     void reset();
@@ -60,6 +62,7 @@ private:
     Event *const m_event;
     Tool *m_lastTool = nullptr; // Used to call the cleanup function of the last tool
     PluginFormManager *const mPluginFormManager;
+    CustomElementsManager *const mCustomElementsManager;
 
     ApplicationContext *const m_applicationContext;
 };
diff --git a/src/widgets/customelementswidget/customelementswidget.cpp b/src/widgets/customelementswidget/customelementswidget.cpp
new file mode 100644
index 00000000..db0c7a15
--- /dev/null
+++ b/src/widgets/customelementswidget/customelementswidget.cpp
@@ -0,0 +1,27 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Laurent Montel <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+#include "customelementswidget.hpp"
+#include "customelements/customelementsmanager.hpp"
+#include "customelements/customelementsmodel.hpp"
+#include <QListView>
+#include <QVBoxLayout>
+using namespace Qt::Literals::StringLiterals;
+CustomElementsWidget::CustomElementsWidget(CustomElementsManager *manager, QWidget *parent)
+    : QWidget{parent}
+    , mListView(new QListView(this))
+{
+    auto mainLayout = new QVBoxLayout(this);
+    mainLayout->setObjectName(u"mainLayout"_s);
+
+    mListView->setObjectName(u"mListView"_s);
+    if (manager) {
+        mListView->setModel(manager->customElementsModel());
+    }
+    mainLayout->addWidget(mListView);
+}
+
+CustomElementsWidget::~CustomElementsWidget() = default;
+#include "moc_customelementswidget.cpp"
diff --git a/src/widgets/customelementswidget/customelementswidget.hpp b/src/widgets/customelementswidget/customelementswidget.hpp
new file mode 100644
index 00000000..8c181a90
--- /dev/null
+++ b/src/widgets/customelementswidget/customelementswidget.hpp
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Laurent Montel <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+#pragma once
+#include "libdrawywidgets_private_export.h"
+#include <QWidget>
+class QListView;
+class CustomElementsManager;
+class LIBDRAWYWIDGETS_TESTS_EXPORT CustomElementsWidget : public QWidget
+{
+    Q_OBJECT
+public:
+    explicit CustomElementsWidget(CustomElementsManager *manager, QWidget *parent = nullptr);
+    ~CustomElementsWidget() override;
+
+private:
+    QListView *const mListView;
+};
diff --git a/src/widgets/keybindings/actionmanager.cpp b/src/widgets/keybindings/actionmanager.cpp
index ffd41629..0c69696b 100644
--- a/src/widgets/keybindings/actionmanager.cpp
+++ b/src/widgets/keybindings/actionmanager.cpp
@@ -153,6 +153,8 @@ ActionManager::ActionManager(KActionCollection *actionCollection, ApplicationCon
 
     createAction(Action::UnLockItem, i18nc("@action", "Unlock"), {}, this, &ActionManager::slotUnLock)->setIcon(QIcon::fromTheme(u"unlock"_s));
 
+    createAction(Action::AddCustomElement, i18nc("@action", "Add to Librairy"), {}, this, &ActionManager::slotAddCustomElement);
+
     createAction(Action::Debug, i18nc("@action", "Debug"), {}, this, &ActionManager::slotDebug);
 #if HAVE_WHATSNEWSNGSUPPORT
     createAction(Action::WhatsNew, i18nc("@action", "What's new"), {}, this, &ActionManager::slotShowWhatsNew);
@@ -249,6 +251,8 @@ QString ActionManager::actionName(Action type) const
         return u"lock_items"_s;
     case Action::UnLockItem:
         return u"unlock_item"_s;
+    case Action::AddCustomElement:
+        return u"add_custom_element"_s;
     }
     Q_UNREACHABLE();
     return u""_s;
@@ -808,4 +812,13 @@ void ActionManager::slotUpdateZorderAndGroupButtons()
     action(Action::UngroupItems)->setEnabled(atLeastOneGroup);
 }
 
+void ActionManager::slotAddCustomElement()
+{
+    const QList<std::shared_ptr<Item>> items = dynamic_cast<SelectionTool &>(m_context->uiContext()->toolBar()->curTool()).getItemsUnderCursor(m_context);
+    if (items.size() == 1 && items.at(0)->locked()) {
+        // TODO
+    }
+    // TODO
+}
+
 #include "moc_actionmanager.cpp"
diff --git a/src/widgets/keybindings/actionmanager.hpp b/src/widgets/keybindings/actionmanager.hpp
index dfcdb0f0..22963927 100644
--- a/src/widgets/keybindings/actionmanager.hpp
+++ b/src/widgets/keybindings/actionmanager.hpp
@@ -56,6 +56,7 @@ public:
         WhatsNew,
         LockItems,
         UnLockItem,
+        AddCustomElement,
     };
 
     Q_ENUM(Action)
@@ -117,6 +118,7 @@ private:
     void configureShortcuts();
     void exportSelectedElementsToImage();
     void exportToImageElements(const QList<std::shared_ptr<Item>> &items);
+    void slotAddCustomElement();
     QAction *createAction(const Action &actionType, const QString &title, const QList<QKeySequence> &keys);
     QAction *createToolAction(const Action &actionType, const QString &title, const QList<QKeySequence> &keys, Tool::Type toolType);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.