summaryrefslogtreecommitdiff
path: root/src/bookmarks
diff options
context:
space:
mode:
Diffstat (limited to 'src/bookmarks')
-rw-r--r--src/bookmarks/CMakeLists.txt22
-rw-r--r--src/bookmarks/bookmarkmanager.cpp81
-rw-r--r--src/bookmarks/bookmarkmanager.h174
-rw-r--r--src/bookmarks/bookmarkmanager.hpp123
-rw-r--r--src/bookmarks/bookmarkowner.h16
-rw-r--r--src/bookmarks/bookmarkscontextmenu.h4
-rw-r--r--src/bookmarks/bookmarksmenu.h8
-rw-r--r--src/bookmarks/bookmarkstoolbar.h17
-rw-r--r--src/bookmarks/bookmarkstreeformat_xbel.h67
-rw-r--r--src/bookmarks/bookmarkstreeformat_xbel_read.cpp184
-rw-r--r--src/bookmarks/bookmarkstreeformat_xbel_write.cpp142
-rw-r--r--src/bookmarks/bookmarkstreeformats.hpp21
-rw-r--r--src/bookmarks/bookmarkstreeitem.cpp109
-rw-r--r--src/bookmarks/bookmarkstreeitem.hpp108
-rw-r--r--src/bookmarks/bookmarkstreemodel.cpp518
-rw-r--r--src/bookmarks/bookmarkstreemodel.h116
-rw-r--r--src/bookmarks/bookmarkstreemodel.hpp59
-rw-r--r--src/bookmarks/test/xbel-1.0.dtd94
-rw-r--r--src/bookmarks/test/xbel.cpp163
19 files changed, 1307 insertions, 719 deletions
diff --git a/src/bookmarks/CMakeLists.txt b/src/bookmarks/CMakeLists.txt
new file mode 100644
index 00000000..517aa2f6
--- /dev/null
+++ b/src/bookmarks/CMakeLists.txt
@@ -0,0 +1,22 @@
+add_library(bookmarks STATIC
+# bookmarkmanager.cpp bookmarkmanager.h
+# bookmarkowner.cpp bookmarkowner.h
+# bookmarkscontextmenu.cpp bookmarkscontextmenu.h
+# bookmarksmenu.cpp bookmarksmenu.h
+# bookmarkstoolbar.cpp bookmarkstoolbar.h
+ # Bookmarks Model
+ bookmarkstreeitem.cpp bookmarkstreeitem.hpp
+ bookmarkstreemodel.cpp bookmarkstreemodel.hpp
+ bookmarkstreeformats.hpp
+ bookmarkstreeformat_xbel_read.cpp bookmarkstreeformat_xbel_write.cpp
+ # Bookmark Manager
+ bookmarkmanager.cpp bookmarkmanager.hpp
+)
+target_include_directories(bookmarks PUBLIC ${CMAKE_SOURCE_DIR}/src)
+target_link_libraries(bookmarks PUBLIC Qt6::Core Qt6::Widgets)
+
+if(TESTING)
+ add_executable(xbel test/xbel.cpp)
+ target_link_libraries(xbel GTest::gtest GTest::gtest_main bookmarks)
+ gtest_discover_tests(xbel)
+endif()
diff --git a/src/bookmarks/bookmarkmanager.cpp b/src/bookmarks/bookmarkmanager.cpp
index d8001819..1f406a04 100644
--- a/src/bookmarks/bookmarkmanager.cpp
+++ b/src/bookmarks/bookmarkmanager.cpp
@@ -1,80 +1,43 @@
/* ============================================================
-*
-* This file is a part of the rekonq project
-*
-* Copyright (C) 2008-2012 by Andrea Diamantini <adjam7 at gmail dot com>
-* Copyright (C) 2009 by Paweł Prażak <pawelprazak at gmail dot com>
-* Copyright (C) 2009-2010 by Lionel Chauvin <megabigbug@yahoo.fr>
-* Copyright (C) 2010 by Yoann Laissus <yoann dot laissus at gmail dot com>
-*
-*
-* This program is free software; you can redistribute it and/or
-* modify it under the terms of the GNU General Public License as
-* published by the Free Software Foundation; either version 2 of
-* the License or (at your option) version 3 or any later version
-* accepted by the membership of KDE e.V. (or its successor approved
-* by the membership of KDE e.V.), which shall act as a proxy
-* defined in Section 14 of version 3 of the license.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* ============================================================ */
-
+ * The rekonq project
+ * ============================================================
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright (C) 2008-2012 by Andrea Diamantini <adjam7 at gmail dot com>
+ * Copyright (C) 2009 by Paweł Prażak <pawelprazak at gmail dot com>
+ * Copyright (C) 2009-2010 by Lionel Chauvin <megabigbug@yahoo.fr>
+ * Copyright (C) 2010 by Yoann Laissus <yoann dot laissus at gmail dot com>
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks system interface
+ * ============================================================ */
// Self Includes
-#include "bookmarkmanager.h"
-#include "bookmarkmanager.moc"
-
-// Local Includes
-#include "application.h"
-
+#include "bookmarkmanager.hpp"
+#include "bookmarkowner.h"
#include "bookmarksmenu.h"
#include "bookmarkstoolbar.h"
-#include "bookmarkowner.h"
-
-#include "iconmanager.h"
-
-// KDE Includes
-#include <KActionCollection>
-#include <KStandardDirs>
-
-// Qt Includes
-#include <QtCore/QFile>
-
+#include <QFile>
+#include <QStandardPaths>
+#include <memory>
// ----------------------------------------------------------------------------------------------
-
-QWeakPointer<BookmarkManager> BookmarkManager::s_bookmarkManager;
-
+static std::unique_ptr<BookmarkManager> s_bookmarkManager = nullptr;
BookmarkManager *BookmarkManager::self()
{
- if (s_bookmarkManager.isNull())
- {
- s_bookmarkManager = new BookmarkManager(qApp);
- }
- return s_bookmarkManager.data();
+ if (!s_bookmarkManager) { s_bookmarkManager = new BookmarkManager(qApp); }
+ return *s_bookmarkManager;
}
// ----------------------------------------------------------------------------------------------
-
-BookmarkManager::BookmarkManager(QObject *parent)
- : QObject(parent)
- , m_manager(0)
- , m_owner(0)
- , m_actionCollection(new KActionCollection(this))
+BookmarkManager::BookmarkManager(QObject *parent) : QObject(parent)
{
m_manager = KBookmarkManager::userBookmarksManager();
- const QString bookmarksFile = KStandardDirs::locateLocal("data", QString::fromLatin1("konqueror/bookmarks.xml"));
+ const auto bookmarksFile = QStandardPaths::locate(AppLocalDataLocation, QString::fromLatin1("bookmarks.xbel"));
if (!QFile::exists(bookmarksFile))
{
diff --git a/src/bookmarks/bookmarkmanager.h b/src/bookmarks/bookmarkmanager.h
deleted file mode 100644
index 1a76d0d0..00000000
--- a/src/bookmarks/bookmarkmanager.h
+++ /dev/null
@@ -1,174 +0,0 @@
-/* ============================================================
-*
-* This file is a part of the rekonq project
-*
-* Copyright (C) 2008-2012 by Andrea Diamantini <adjam7 at gmail dot com>
-* Copyright (C) 2009 by Paweł Prażak <pawelprazak at gmail dot com>
-* Copyright (C) 2009-2010 by Lionel Chauvin <megabigbug@yahoo.fr>
-* Copyright (C) 2010 by Yoann Laissus <yoann dot laissus at gmail dot com>
-*
-*
-* This program is free software; you can redistribute it and/or
-* modify it under the terms of the GNU General Public License as
-* published by the Free Software Foundation; either version 2 of
-* the License or (at your option) version 3 or any later version
-* accepted by the membership of KDE e.V. (or its successor approved
-* by the membership of KDE e.V.), which shall act as a proxy
-* defined in Section 14 of version 3 of the license.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* ============================================================ */
-
-
-#ifndef BOOKMARK_MANAGER_H
-#define BOOKMARK_MANAGER_H
-
-
-// Rekonq Includes
-#include "rekonq_defines.h"
-
-// KDE Includes
-#include <KBookmark>
-#include <KActionMenu>
-
-// Qt Includes
-#include <QObject>
-#include <QWeakPointer>
-
-// Forward Declarations
-class BookmarkToolBar;
-class BookmarkOwner;
-
-class KAction;
-class KActionCollection;
-class KBookmarkGroup;
-class KBookmarkManager;
-class KUrl;
-
-class QAction;
-
-
-/**
- * This class represent the interface to rekonq bookmarks system.
- * All rekonq needs (Bookmarks Menu, Bookmarks Toolbar) is provided
- * from this class.
- * So it implements code to have each one.
- */
-class BookmarkManager : public QObject
-{
- Q_OBJECT
-
-public:
- /**
- * Entry point.
- * Access to BookmarkManager class by using
- * BookmarkManager::self()->thePublicMethodYouNeed()
- */
- static BookmarkManager *self();
-
- ~BookmarkManager();
-
- /**
- * @short set the Bookmarks Toolbar Action
- */
- void registerBookmarkBar(BookmarkToolBar *toolbar);
- void removeBookmarkBar(BookmarkToolBar *toolbar);
-
- /**
- * @short Get action by name
- * This method returns poiner bookmark action of given name.
- * @pre m_actionCollection != NULL
- * @param name Name of action you want to get
- * @return It returns actions if one exists or empty object
- */
- QAction* actionByName(const QString &name);
-
- /**
- * returns Bookmark Manager root group
- *
- * @return the root bookmark group
- */
- KBookmarkGroup rootGroup();
-
- inline KBookmarkManager* manager()
- {
- return m_manager;
- }
-
- inline BookmarkOwner* owner()
- {
- return m_owner;
- }
-
- QList<KBookmark> find(const QString &text);
-
- KBookmark bookmarkForUrl(const KUrl &url);
-
- KBookmark findByAddress(const QString &);
-
- void openFolderinTabs(const KBookmarkGroup &bm);
-
- void emitChanged();
-
- static inline QString bookmark_mime_type()
- {
- return QL1S("application/x-rekonq-bookmark");
- }
-
- KActionMenu* bookmarkActionMenu(QWidget *parent);
-
-private:
- /**
- * @short Class constructor.
- * Connect BookmarksProvider with bookmarks source
- * (actually konqueror's bookmarks).
- * @param parent The WebWindow to provide bookmarks objects.
- */
- explicit BookmarkManager(QObject *parent = 0);
-
-public Q_SLOTS:
- /**
- * @short Waits for signal that the group with the address has been modified by the caller.
- * Waits for signal that the group (or any of its children) with the address
- * @p groupAddress (e.g. "/4/5") has been modified by the caller @p caller.
- * @param groupAddress bookmark group address
- * @param caller caller that modified the bookmarks
- * @see KBookmarkManager::changed
- */
- void slotBookmarksChanged();
- void fillBookmarkBar(BookmarkToolBar *toolBar);
-
- void slotEditBookmarks();
-
- KBookmark bookmarkCurrentPage(const KBookmark &bookmark = KBookmark());
-
-Q_SIGNALS:
- /**
- * @short This signal is emitted when an url has to be loaded
- */
- void openUrl(const KUrl &, const Rekonq::OpenType &);
-
- void bookmarksUpdated();
-
-private:
- void find(QList<KBookmark> *list, const KBookmark &bookmark, const QString &text);
- KBookmark bookmarkForUrl(const KBookmark &bookmark, const KUrl &url);
- void copyBookmarkGroup(const KBookmarkGroup &groupToCopy, KBookmarkGroup destGroup);
-
- KBookmarkManager *m_manager;
- BookmarkOwner *m_owner;
- KActionCollection *m_actionCollection;
- QList<BookmarkToolBar *> m_bookmarkToolBars;
-
- static QWeakPointer<BookmarkManager> s_bookmarkManager;
-};
-
-
-#endif // BOOKMARK_MANAGER_H
diff --git a/src/bookmarks/bookmarkmanager.hpp b/src/bookmarks/bookmarkmanager.hpp
new file mode 100644
index 00000000..417eb83d
--- /dev/null
+++ b/src/bookmarks/bookmarkmanager.hpp
@@ -0,0 +1,123 @@
+/* ============================================================
+ * The rekonq project
+ * ============================================================
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright (C) 2008-2012 by Andrea Diamantini <adjam7 at gmail dot com>
+ * Copyright (C) 2009 by Paweł Prażak <pawelprazak at gmail dot com>
+ * Copyright (C) 2009-2010 by Lionel Chauvin <megabigbug@yahoo.fr>
+ * Copyright (C) 2010 by Yoann Laissus <yoann dot laissus at gmail dot com>
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks system interface
+ * ============================================================ */
+
+#pragma once
+
+#include "bookmarkstreemodel.hpp"
+#include "rekonq.hpp"
+#include <QObject>
+
+class BookmarkToolBar;
+class QAction;
+
+/**
+ * This class represent the interface to rekonq bookmarks system.
+ * All rekonq needs (Bookmarks Menu, Bookmarks Toolbar) is provided
+ * from this class.
+ * So it implements code to have each one.
+ */
+class BookmarkManager : public QObject {
+ Q_OBJECT
+
+public:
+ /**
+ * Entry point.
+ * Access to BookmarkManager class by using
+ * BookmarkManager::self()->thePublicMethodYouNeed()
+ */
+ static BookmarkManager *self();
+
+ ~BookmarkManager();
+
+ /**
+ * @short set the Bookmarks Toolbar Action
+ */
+ void registerBookmarkBar(BookmarkToolBar *toolbar);
+ void removeBookmarkBar(BookmarkToolBar *toolbar);
+
+ /**
+ * @short Get action by name
+ * This method returns poiner bookmark action of given name.
+ * @pre m_actionCollection != NULL
+ * @param name Name of action you want to get
+ * @return It returns actions if one exists or empty object
+ */
+ QAction *actionByName(const QString &name);
+
+ /**
+ * returns Bookmark Manager root group
+ *
+ * @return the root bookmark group
+ */
+ KBookmarkGroup rootGroup();
+
+ inline KBookmarkManager *manager() { return m_manager; }
+
+ inline BookmarkOwner *owner() { return m_owner; }
+
+ QList<KBookmark> find(const QString &text);
+
+ KBookmark bookmarkForUrl(const KUrl &url);
+
+ KBookmark findByAddress(const QString &);
+
+ void openFolderinTabs(const KBookmarkGroup &bm);
+
+ void emitChanged();
+
+ static inline QString bookmark_mime_type() { return QL1S("application/x-rekonq-bookmark"); }
+
+ KActionMenu *bookmarkActionMenu(QWidget *parent);
+
+private:
+ /**
+ * @short Class constructor.
+ * Connect BookmarksProvider with bookmarks source
+ * (actually konqueror's bookmarks).
+ * @param parent The WebWindow to provide bookmarks objects.
+ */
+ explicit BookmarkManager(QObject *parent = nullptr);
+
+public Q_SLOTS:
+ /**
+ * @short Waits for signal that the group with the address has been modified by the caller.
+ * Waits for signal that the group (or any of its children) with the address
+ * @p groupAddress (e.g. "/4/5") has been modified by the caller @p caller.
+ * @param groupAddress bookmark group address
+ * @param caller caller that modified the bookmarks
+ * @see KBookmarkManager::changed
+ */
+ void slotBookmarksChanged();
+ void fillBookmarkBar(BookmarkToolBar *toolBar);
+
+ void slotEditBookmarks();
+
+ KBookmark bookmarkCurrentPage(const KBookmark &bookmark = KBookmark());
+
+Q_SIGNALS:
+ /**
+ * @short This signal is emitted when an url has to be loaded
+ */
+ void openUrl(const KUrl &, const Rekonq::OpenType &);
+
+ void bookmarksUpdated();
+
+private:
+ void find(QList<KBookmark> *list, const KBookmark &bookmark, const QString &text);
+ KBookmark bookmarkForUrl(const KBookmark &bookmark, const KUrl &url);
+ void copyBookmarkGroup(const KBookmarkGroup &groupToCopy, KBookmarkGroup destGroup);
+
+ QList<BookmarkToolBar *> m_bookmarkToolBars;
+ BookmarkModel *m_model;
+};
diff --git a/src/bookmarks/bookmarkowner.h b/src/bookmarks/bookmarkowner.h
index 5626373b..284c9459 100644
--- a/src/bookmarks/bookmarkowner.h
+++ b/src/bookmarks/bookmarkowner.h
@@ -30,21 +30,14 @@
#ifndef BOOKMARKOWNER_H
#define BOOKMARKOWNER_H
-
-// Rekonq Includes
-#include "rekonq_defines.h"
-
-// KDE Includes
-#include <KAction>
-#include <KBookmarkOwner>
-
+#include "rekonq.hpp"
+#include <QObject>
/**
* This class allows to manage bookmarks as actions.
*/
-class REKONQ_TESTS_EXPORT BookmarkOwner : public QObject, public KBookmarkOwner
-{
- Q_OBJECT
+class REKONQ_TESTS_EXPORT BookmarkOwner : public QObject {
+ Q_OBJECT
public:
explicit BookmarkOwner(KBookmarkManager *manager, QObject *parent = 0);
@@ -132,7 +125,6 @@ private:
KBookmarkManager *m_manager;
};
-
// -----------------------------------------------------------------------------------------------
diff --git a/src/bookmarks/bookmarkscontextmenu.h b/src/bookmarks/bookmarkscontextmenu.h
index b98e3408..2c122be4 100644
--- a/src/bookmarks/bookmarkscontextmenu.h
+++ b/src/bookmarks/bookmarkscontextmenu.h
@@ -27,10 +27,6 @@
#ifndef BOOKMARKS_CONTEXT_MENU_H
#define BOOKMARKS_CONTEXT_MENU_H
-
-// KDE Includes
-#include <KBookmarkMenu>
-
// Forward Declarations
class BookmarkOwner;
diff --git a/src/bookmarks/bookmarksmenu.h b/src/bookmarks/bookmarksmenu.h
index 36b3bca5..2b690d91 100644
--- a/src/bookmarks/bookmarksmenu.h
+++ b/src/bookmarks/bookmarksmenu.h
@@ -28,13 +28,7 @@
#ifndef BOOKMARKS_MENU_H
#define BOOKMARKS_MENU_H
-
-// Rekonq Includes
-#include "rekonq_defines.h"
-
-// KDE Includes
-#include <KBookmarkMenu>
-
+#include "rekonq.hpp"
/**
* This class represent the rekonq bookmarks menu.
diff --git a/src/bookmarks/bookmarkstoolbar.h b/src/bookmarks/bookmarkstoolbar.h
index 104debce..d325def4 100644
--- a/src/bookmarks/bookmarkstoolbar.h
+++ b/src/bookmarks/bookmarkstoolbar.h
@@ -28,25 +28,16 @@
#ifndef BOOKMARKSTOOLBAR_H
#define BOOKMARKSTOOLBAR_H
-
-// Rekonq Includes
-#include "rekonq_defines.h"
-
-// KDE Includes
-#include <KToolBar>
-
-// Forward Declarations
-class KMenu;
-
+#include "rekonq.hpp"
+#include <QToolBar>
/**
* This class manage the bookmark toolbar.
* Some events from the toolbar are handled to allow the drag and drop
*/
-class BookmarkToolBar : public KToolBar
-{
- Q_OBJECT
+class BookmarkToolBar : public QToolBar {
+ Q_OBJECT
public:
explicit BookmarkToolBar(QWidget *parent);
diff --git a/src/bookmarks/bookmarkstreeformat_xbel.h b/src/bookmarks/bookmarkstreeformat_xbel.h
new file mode 100644
index 00000000..c156eb24
--- /dev/null
+++ b/src/bookmarks/bookmarkstreeformat_xbel.h
@@ -0,0 +1,67 @@
+/* ============================================================
+ * rekonq
+ * ============================================================
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks model
+ * ============================================================ */
+
+#pragma once
+
+#include <QVector>
+
+class QIODevice;
+class BookmarksTreeItem;
+
+namespace xbel {
+/**
+ * Parse an XBEL-formatted BookmarkItem tree
+ * @param device QIODEvice pointer to read from
+ * @param root BookmarkItem tree root pointer
+ * @return true if parsing was successful, false if errors occurred
+ */
+[[nodiscard]] bool read(QIODevice *device, BookmarksTreeItem *root);
+
+/**
+ * Write items into device
+ * @param device
+ * @param items
+ * @return true on success
+ */
+[[nodiscard]] bool write(QIODevice *device, const QVector<const BookmarksTreeItem *> &items);
+
+// 3.2 Top-level Information
+constexpr auto *elem_xbel{"xbel"};
+constexpr auto *attr_version{"version"};
+constexpr auto *attr_version_value{"1.0"};
+
+// 3.3 Common Elements
+static const QLatin1String elem_title{"title"};
+static const QLatin1String elem_desc{"desc"};
+static const QLatin1String elem_info{"info"};
+static const QLatin1String elem_metadata{"metadata"};
+static const QLatin1String elem_metadata_owner{"owner"};
+static const QLatin1String elem_metadata_owner_value{"rekonq"};
+
+// 3.4 Data Organization
+static const QLatin1String elem_bookmark{"bookmark"};
+static const QLatin1String elem_folder{"folder"};
+static const QLatin1String elem_separator{"separator"};
+static const QLatin1String elem_alias{"alias"};
+
+// node attributes
+constexpr auto *attr_id{"id"};
+constexpr auto *attr_added{"added"};
+
+// url attributes
+constexpr auto *attr_href{"href"};
+constexpr auto *attr_visited{"visited"};
+constexpr auto *attr_modified{"modified"};
+
+constexpr auto *attr_ref{"ref"};
+constexpr auto *attr_folded{"folded"};
+constexpr auto *attr_folded_yes{"yes"};
+constexpr auto *attr_folded_no{"no"};
+
+} // namespace xbel
diff --git a/src/bookmarks/bookmarkstreeformat_xbel_read.cpp b/src/bookmarks/bookmarkstreeformat_xbel_read.cpp
new file mode 100644
index 00000000..e16eda27
--- /dev/null
+++ b/src/bookmarks/bookmarkstreeformat_xbel_read.cpp
@@ -0,0 +1,184 @@
+/* ============================================================
+ * rekonq
+ * ============================================================
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks model
+ * ============================================================ */
+
+#include "bookmarkstreeformat_xbel.h"
+#include "bookmarkstreeitem.hpp"
+#include <QDateTime>
+#include <QXmlStreamReader>
+
+inline void readNodeAttributes(BookmarksTreeItem *item, const QXmlStreamAttributes &attributes)
+{
+ // id
+ if (attributes.hasAttribute(xbel::attr_id))
+ item->setData(BookmarksTreeItem::Id, attributes.value(xbel::attr_id).toString());
+
+ // added
+ if (attributes.hasAttribute(xbel::attr_added)) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ auto dateTime = QDateTime::fromString(attributes.value(xbel::attr_added), Qt::ISODate);
+#else
+ auto dateTime = QDateTime::fromString(attributes.value(xbel::attr_added).toString(), Qt::ISODate);
+#endif
+ if (!dateTime.isNull() && dateTime.isValid()) item->setData(BookmarksTreeItem::Added, dateTime);
+ }
+}
+
+inline void readUrlAttributes(BookmarksTreeItem *item, const QXmlStreamAttributes &attributes)
+{
+ // href (required)
+ if (attributes.hasAttribute(xbel::attr_href))
+ item->setData(BookmarksTreeItem::Href, attributes.value(xbel::attr_href).toString());
+
+ // visited
+ if (attributes.hasAttribute(xbel::attr_visited)) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ auto dateTime = QDateTime::fromString(attributes.value(xbel::attr_visited), Qt::ISODate);
+#else
+ auto dateTime = QDateTime::fromString(attributes.value(xbel::attr_visited).toString(), Qt::ISODate);
+#endif
+ if (!dateTime.isNull() && dateTime.isValid()) item->setData(BookmarksTreeItem::Visited, dateTime);
+ }
+
+ // modified
+ if (attributes.hasAttribute(xbel::attr_modified)) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ auto dateTime = QDateTime::fromString(attributes.value(xbel::attr_modified), Qt::ISODate);
+#else
+ auto dateTime = QDateTime::fromString(attributes.value(xbel::attr_modified).toString(), Qt::ISODate);
+#endif
+ if (!dateTime.isNull() && dateTime.isValid()) item->setData(BookmarksTreeItem::Modified, dateTime);
+ }
+}
+
+[[nodiscard]] inline bool readItemAttributes(BookmarksTreeItem *item, const QXmlStreamAttributes &attr)
+{
+ bool has_errors = false;
+
+ switch (item->type()) {
+
+ case BookmarksTreeItem::Root:
+ if (!attr.hasAttribute(xbel::attr_version)) {
+ has_errors = true;
+ // spdlog::error("xbel::read: xbel has no version");
+ }
+ else if (attr.value(xbel::attr_version).compare(QLatin1String{xbel::attr_version_value}) != 0) {
+ has_errors = true;
+ // spdlog::error("xbel::read: unknown xbel version");
+ }
+ readNodeAttributes(item, attr);
+ break;
+
+ case BookmarksTreeItem::Folder:
+ readNodeAttributes(item, attr);
+ if (attr.hasAttribute(xbel::attr_folded))
+ item->setExpanded(attr.value(xbel::attr_folded).compare(QLatin1String{xbel::attr_folded_yes}) != 0);
+ break;
+
+ case BookmarksTreeItem::Separator:
+ break;
+
+ case BookmarksTreeItem::Bookmark:
+ readNodeAttributes(item, attr);
+ readUrlAttributes(item, attr);
+ break;
+
+ case BookmarksTreeItem::Alias:
+ if (!attr.hasAttribute(xbel::attr_ref)) {
+ has_errors = true;
+ // spdlog::error("xbel::read: Alias has no ref");
+ }
+ else
+ item->setData(BookmarksTreeItem::Id, attr.value(xbel::attr_ref).toString());
+ break;
+
+ default:
+ return false;
+ }
+
+ return has_errors;
+}
+
+/**
+ * Recursively parse XBEL
+ * @param reader
+ * @param parent
+ * @return true if there were errors during parsing
+ */
+[[nodiscard]] inline bool readChildElements(QXmlStreamReader &reader, BookmarksTreeItem *parent)
+{
+ bool has_errors = false;
+
+ while (reader.readNextStartElement()) {
+ if (reader.hasError()) {
+ has_errors = true;
+ // spdlog::warn("xbel::read: error during parsing: {}", qUtf8Printable(reader.errorString()));
+ }
+
+ const auto name = reader.name();
+ if (name == xbel::elem_title) { parent->setData(BookmarksTreeItem::Title, reader.readElementText()); }
+ else if (name == xbel::elem_desc) {
+ parent->setData(BookmarksTreeItem::Description, reader.readElementText());
+ }
+ else if (name == xbel::elem_info) {
+ has_errors += readChildElements(reader, parent);
+ }
+ else if (name == xbel::elem_metadata) {
+ if (reader.attributes().value(xbel::elem_metadata_owner) == QLatin1String{xbel::elem_metadata_owner_value})
+ parent->metadata.append(reader.readElementText());
+ else
+ reader.skipCurrentElement();
+ }
+ else if (name == xbel::elem_bookmark) {
+
+ auto *item = new BookmarksTreeItem(BookmarksTreeItem::Bookmark, {}, parent);
+ has_errors += readItemAttributes(item, reader.attributes());
+ if (!parent->appendChild(item)) has_errors = true;
+ has_errors += readChildElements(reader, item);
+ }
+ else if (name == xbel::elem_folder) {
+ auto *item = new BookmarksTreeItem(BookmarksTreeItem::Folder, {}, parent);
+ has_errors += readItemAttributes(item, reader.attributes());
+ if (!parent->appendChild(item)) has_errors = true;
+ has_errors += readChildElements(reader, item);
+ }
+ else if (name == xbel::elem_separator) {
+ auto *item = new BookmarksTreeItem(BookmarksTreeItem::Separator, {}, parent);
+ if (!parent->appendChild(item)) has_errors = true;
+ reader.skipCurrentElement();
+ }
+ else if (name == xbel::elem_alias) {
+ auto *item = new BookmarksTreeItem(BookmarksTreeItem::Alias, {}, parent);
+ item->setData(BookmarksTreeItem::Id, reader.attributes().value(xbel::attr_ref).toString());
+ if (!parent->appendChild(item)) has_errors = true;
+ reader.skipCurrentElement();
+ }
+ else {
+ // spdlog::error("xbel::read: skipping unknown element {} on line {}", qUtf8Printable(name.toString()),
+ // reader.lineNumber());
+ has_errors = true;
+ reader.skipCurrentElement();
+ }
+ }
+
+ return has_errors;
+}
+
+bool xbel::read(QIODevice *device, BookmarksTreeItem *root)
+{
+ if (!device->isOpen()) return false;
+
+ QXmlStreamReader reader(device);
+ if (!reader.readNextStartElement()) return false;
+
+ // parse the top-level item attributes
+ if (reader.name().compare(QLatin1String{elem_xbel}) != 0) return false;
+ if (readItemAttributes(root, reader.attributes())) return false;
+
+ return !readChildElements(reader, root);
+}
diff --git a/src/bookmarks/bookmarkstreeformat_xbel_write.cpp b/src/bookmarks/bookmarkstreeformat_xbel_write.cpp
new file mode 100644
index 00000000..54ed625e
--- /dev/null
+++ b/src/bookmarks/bookmarkstreeformat_xbel_write.cpp
@@ -0,0 +1,142 @@
+/* ============================================================
+ * rekonq
+ * ============================================================
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks model
+ * ============================================================ */
+
+#include "bookmarkstreeformat_xbel.h"
+#include "bookmarkstreeitem.hpp"
+#include <QXmlStreamReader>
+
+inline void writeNodeAttributes(QXmlStreamWriter &writer, const BookmarksTreeItem *item)
+{
+ // id
+ if (const auto id = item->data(BookmarksTreeItem::Id).toString(); !id.isEmpty())
+ writer.writeAttribute(xbel::attr_id, id);
+
+ // added
+ if (const auto added = item->data(BookmarksTreeItem::Added).toDateTime(); !added.isNull())
+ writer.writeAttribute(xbel::attr_added, added.toString(Qt::ISODate));
+}
+
+inline void writeUrlAttributes(QXmlStreamWriter &writer, const BookmarksTreeItem *item)
+{
+ // href (required)
+ writer.writeAttribute(xbel::attr_href, item->data(BookmarksTreeItem::Href).toString());
+
+ // visited
+ if (const auto visited = item->data(BookmarksTreeItem::Visited).toDateTime(); !visited.isNull())
+ writer.writeAttribute(xbel::attr_visited, visited.toString(Qt::ISODate));
+
+ // modified
+ if (const auto modified = item->data(BookmarksTreeItem::Modified).toDateTime(); !modified.isNull())
+ writer.writeAttribute(xbel::attr_modified, modified.toString(Qt::ISODate));
+}
+
+inline void writeItemTitle(QXmlStreamWriter &writer, const BookmarksTreeItem *item)
+{
+ if (const auto title = item->data(BookmarksTreeItem::Title).toString(); !title.isEmpty())
+ writer.writeTextElement(xbel::elem_title, title);
+}
+
+inline void writeItemInfo(QXmlStreamWriter &writer, const BookmarksTreeItem *item)
+{
+ if (!item->metadata.isEmpty()) {
+ writer.writeStartElement(xbel::elem_info);
+ for (const auto &meta : item->metadata) {
+ writer.writeStartElement(xbel::elem_metadata);
+ writer.writeAttribute(xbel::elem_metadata_owner, xbel::elem_metadata_owner_value);
+ writer.writeCDATA(meta);
+ writer.writeEndElement(); // elem_metadata
+ }
+ writer.writeEndElement(); // elem_info
+ }
+}
+
+inline void writeItemDesc(QXmlStreamWriter &writer, const BookmarksTreeItem *item)
+{
+ if (const auto desc = item->data(BookmarksTreeItem::Description).toString(); !desc.isEmpty())
+ writer.writeTextElement(xbel::elem_desc, desc);
+}
+
+inline void writeChildElements(QXmlStreamWriter &writer, const BookmarksTreeItem *item)
+{
+ switch (item->type()) {
+ case BookmarksTreeItem::Root:
+ // node.att
+ writeNodeAttributes(writer, item);
+ // title
+ writeItemTitle(writer, item);
+ // info
+ writeItemInfo(writer, item);
+ // desc
+ writeItemDesc(writer, item);
+ // children
+ for (int i = 0; i < item->childCount(); ++i) { writeChildElements(writer, item->child(i)); }
+ break;
+
+ case BookmarksTreeItem::Folder:
+ writer.writeStartElement(xbel::elem_folder);
+ // note.att
+ writeNodeAttributes(writer, item);
+ // folded
+ writer.writeAttribute(xbel::attr_folded, !item->isExpanded() ? xbel::attr_folded_yes : xbel::attr_folded_no);
+ // title
+ writeItemTitle(writer, item);
+ // info
+ writeItemInfo(writer, item);
+ // desc
+ writeItemDesc(writer, item);
+ // children
+ for (int i = 0; i < item->childCount(); ++i) { writeChildElements(writer, item->child(i)); }
+
+ break;
+
+ case BookmarksTreeItem::Bookmark:
+ writer.writeStartElement(xbel::elem_bookmark);
+ // node.att
+ writeNodeAttributes(writer, item);
+ // url.att
+ writeUrlAttributes(writer, item);
+ // title
+ writeItemTitle(writer, item);
+ // info
+ writeItemInfo(writer, item);
+ // desc
+ writeItemDesc(writer, item);
+ break;
+
+ case BookmarksTreeItem::Separator:
+ writer.writeStartElement(xbel::elem_separator);
+ // empty
+ break;
+
+ case BookmarksTreeItem::Alias:
+ writer.writeStartElement(xbel::elem_alias);
+ // ref
+ writer.writeAttribute(xbel::attr_ref, item->data(BookmarksTreeItem::Id).toString());
+ break;
+ }
+
+ writer.writeEndElement();
+}
+
+bool xbel::write(QIODevice *device, const QVector<const BookmarksTreeItem *> &items)
+{
+ QXmlStreamWriter xmlWriter(device);
+ xmlWriter.setAutoFormatting(true);
+
+ xmlWriter.writeStartDocument();
+ xmlWriter.writeDTD("<!DOCTYPE xbel>");
+
+ xmlWriter.writeStartElement(xbel::elem_xbel);
+ xmlWriter.writeAttribute(xbel::attr_version, xbel::attr_version_value);
+
+ for (const auto *item : items) writeChildElements(xmlWriter, item);
+
+ xmlWriter.writeEndDocument();
+ return true;
+}
diff --git a/src/bookmarks/bookmarkstreeformats.hpp b/src/bookmarks/bookmarkstreeformats.hpp
new file mode 100644
index 00000000..380a7e64
--- /dev/null
+++ b/src/bookmarks/bookmarkstreeformats.hpp
@@ -0,0 +1,21 @@
+/* ============================================================
+ * rekonq
+ * ============================================================
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks model
+ * ============================================================ */
+
+#pragma once
+
+#include "bookmarkstreeformat_xbel.h"
+#include <array>
+
+typedef bool (*t_readFn)(QIODevice *, BookmarksTreeItem *);
+typedef bool (*t_writeFn)(QIODevice *, const QVector<const BookmarksTreeItem *> &);
+
+enum Formats { FormatXbel = 0 };
+
+constexpr std::array<t_readFn, 1> readFns{&xbel::read};
+constexpr std::array<t_writeFn, 1> writeFns{&xbel::write};
diff --git a/src/bookmarks/bookmarkstreeitem.cpp b/src/bookmarks/bookmarkstreeitem.cpp
new file mode 100644
index 00000000..fba1cc19
--- /dev/null
+++ b/src/bookmarks/bookmarkstreeitem.cpp
@@ -0,0 +1,109 @@
+/* ============================================================
+ * rekonq
+ * ============================================================
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks model
+ * ============================================================ */
+
+#include "bookmarkstreeitem.hpp"
+
+BookmarksTreeItem::BookmarksTreeItem(Types type, Attributes_t &&args, BookmarksTreeItem *parent)
+ : m_parent(parent), m_type(type), m_data(args)
+{
+ switch (m_type) {
+ case Folder:
+ m_icon = QIcon::fromTheme("folder");
+ break;
+ case Bookmark:
+ m_icon = QIcon::fromTheme("bookmark");
+ break;
+ case Alias:
+ case Root:
+ case Separator:
+ break;
+ }
+}
+
+BookmarksTreeItem::~BookmarksTreeItem() { qDeleteAll(m_children); }
+
+bool BookmarksTreeItem::appendChild(BookmarksTreeItem *childItem)
+{
+ // only root and folders can have children
+ if (m_type == Folder || m_type == Root) {
+ m_children.append(childItem);
+ return true;
+ }
+ return false;
+}
+
+bool BookmarksTreeItem::insertChild(int position, BookmarksTreeItem *childItem)
+{
+ if (m_type == Folder || m_type == Root) {
+ // position is invalid (-1) when dropping an item onto the folder, which leads to crash
+ // make sure that position passed is >= 0 (insert item at first position)
+ childItem->m_parent = this;
+ m_children.insert(qMax(position, 0), childItem);
+ return true;
+ }
+
+ return false;
+}
+
+bool BookmarksTreeItem::removeChildAt(int index, int count)
+{
+ if (index < 0 || index + count > m_children.size()) return false;
+
+ // delete the item at index count times
+ for (int i = 0; i < count; ++i) { delete m_children.takeAt(index); }
+ return true;
+}
+
+bool BookmarksTreeItem::setData(Attributes column, const QVariant &data)
+{
+ switch (column) {
+ case Title:
+ m_data.title = data.toString();
+ return true;
+ case Href:
+ m_data.href = data.toUrl();
+ return true;
+ case Added:
+ m_data.added = data.toDateTime();
+ return true;
+ case Visited:
+ m_data.visited = data.toDateTime();
+ return true;
+ case Modified:
+ m_data.modified = data.toDateTime();
+ return true;
+ case Description:
+ m_data.description = data.toString();
+ return true;
+ case Id:
+ m_data.id = data.toString();
+ return true;
+ }
+
+ return false;
+}
+
+void BookmarksTreeItem::setExpanded(bool expanded)
+{
+ if (m_type == BookmarksTreeItem::Folder) m_isExpanded = expanded;
+}
+
+QString BookmarksTreeItem::tooltip() const
+{
+ auto msg = QString("<p><b>%1</b></p>").arg(m_data.title);
+ if (const auto href = m_data.href.toString(); !href.isEmpty())
+ msg += QString("<p>%2</p>").arg(m_data.href.toString());
+ return msg;
+}
+
+int BookmarksTreeItem::row() const
+{
+ if (m_parent) return static_cast<int>(m_parent->m_children.indexOf(const_cast<BookmarksTreeItem *>(this)));
+ return 0;
+}
diff --git a/src/bookmarks/bookmarkstreeitem.hpp b/src/bookmarks/bookmarkstreeitem.hpp
new file mode 100644
index 00000000..e8cd5a90
--- /dev/null
+++ b/src/bookmarks/bookmarkstreeitem.hpp
@@ -0,0 +1,108 @@
+/* ============================================================
+ * rekonq
+ * ============================================================
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks model
+ * ============================================================ */
+
+#pragma once
+
+#include "rekonq.hpp"
+#include <QDateTime>
+#include <QIcon>
+#include <QUrl>
+#include <QVariant>
+#include <QVector>
+#include <utility>
+
+class BookmarksTreeItem {
+public:
+ enum Types { Root, Folder, Bookmark, Separator, Alias };
+ constexpr static int TypesCount = 5;
+
+ struct Attributes_t {
+ QString title{};
+ QUrl href{};
+ QDateTime added{};
+ QDateTime visited{};
+ QDateTime modified{};
+ QString description{};
+ QString id{};
+ };
+ enum Attributes { Title = 0, Href = 1, Added = 2, Visited = 3, Modified = 4, Description = 5, Id = 6 };
+ constexpr static int AttributeCount = 7;
+
+ explicit BookmarksTreeItem(Types type, Attributes_t &&args, BookmarksTreeItem *parent);
+ ~BookmarksTreeItem();
+
+ [[nodiscard]] BookmarksTreeItem *parent() const { return m_parent; }
+
+ /**
+ * Add an item to the end of the children list, if the item can have children
+ * @return true if the item was added, false otherwise
+ */
+ bool appendChild(BookmarksTreeItem *childItem);
+ bool insertChild(int position, BookmarksTreeItem *childItem);
+ bool removeChildAt(int index, int count = 1);
+ [[nodiscard]] BookmarksTreeItem *takeChild(int index) { return m_children.takeAt(index); }
+
+ [[nodiscard]] BookmarksTreeItem *child(int index) const { return m_children.value(index); }
+ [[nodiscard]] auto childCount() const { return m_children.count(); }
+
+ [[nodiscard]] BookmarksTreeItem *findChild(const QString &id)
+ {
+ if (id.isEmpty()) return nullptr;
+ if (m_data.id == id) return this;
+
+ for (auto *child : qAsConst(m_children))
+ if (child->m_data.id == id) return child;
+ for (auto *child : qAsConst(m_children))
+ if (auto *grandchild = child->findChild(id)) return grandchild;
+
+ return nullptr;
+ }
+
+ [[nodiscard]] QVariant data(Attributes column) const
+ {
+ switch (column) {
+ case Title:
+ return m_data.title;
+ case Href:
+ return m_data.href;
+ case Added:
+ return m_data.added;
+ case Visited:
+ return m_data.visited;
+ case Modified:
+ return m_data.modified;
+ case Description:
+ return m_data.description;
+ case Id:
+ return m_data.id;
+ }
+ return {};
+ }
+ bool setData(Attributes column, const QVariant &data);
+
+ [[nodiscard]] auto icon() const { return m_icon; }
+ [[nodiscard]] bool isExpanded() const { return m_isExpanded; }
+ void setExpanded(bool expanded);
+
+ [[nodiscard]] QString tooltip() const;
+ [[nodiscard]] Types type() const { return m_type; }
+ [[nodiscard]] int row() const;
+
+ QStringList metadata;
+
+private:
+ BookmarksTreeItem *m_parent;
+ QVector<BookmarksTreeItem *> m_children;
+
+ const Types m_type;
+ QIcon m_icon;
+ bool m_isExpanded = false;
+
+ Attributes_t m_data;
+};
diff --git a/src/bookmarks/bookmarkstreemodel.cpp b/src/bookmarks/bookmarkstreemodel.cpp
index 45b50253..444d2e7b 100644
--- a/src/bookmarks/bookmarkstreemodel.cpp
+++ b/src/bookmarks/bookmarkstreemodel.cpp
@@ -1,406 +1,256 @@
/* ============================================================
-*
-* This file is a part of the rekonq project
-*
-* Copyright (C) 2009 by Nils Weigel <nehlsen at gmail dot com>
-* Copyright (C) 2010-2013 by Andrea Diamantini <adjam7 at gmail dot com>
-*
-*
-* This program is free software; you can redistribute it and/or
-* modify it under the terms of the GNU General Public License as
-* published by the Free Software Foundation; either version 2 of
-* the License or (at your option) version 3 or any later version
-* accepted by the membership of KDE e.V. (or its successor approved
-* by the membership of KDE e.V.), which shall act as a proxy
-* defined in Section 14 of version 3 of the license.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* ============================================================ */
-
-
-// Self Includes
-#include "bookmarkstreemodel.h"
-#include "bookmarkstreemodel.moc"
-
-// Local Includes
-#include "bookmarkmanager.h"
-#include "iconmanager.h"
-
-// KDE Includes
-#include <KBookmarkManager>
-#include <KLocalizedString>
-#include <KIcon>
-
-// Qt Includes
-#include <QtCore/QMimeData>
-
-
-BtmItem::BtmItem(const KBookmark &bm)
- : m_parent(0)
- , m_kbm(bm)
+ * rekonq
+ * ============================================================
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks model
+ * ============================================================ */
+
+#include "bookmarkstreemodel.hpp"
+#include "bookmarkstreeformats.hpp"
+#include <QBuffer>
+#include <QDateTime>
+#include <QFile>
+#include <QMimeData>
+
+BookmarkModel::BookmarkModel(const QString &path, QObject *parent)
+ : QAbstractItemModel(parent), bookmarksFile(new QFile(path))
{
-}
-
-
-BtmItem::~BtmItem()
-{
- qDeleteAll(m_children);
-}
-
+ rootItem = new BookmarksTreeItem(BookmarksTreeItem::Root, {.title = tr("Title"), .href = tr("Address")}, nullptr);
-QVariant BtmItem::data(int role) const
-{
- if (m_kbm.isNull())
- return QVariant(); // should only happen for root item
-
- if (role == Qt::DisplayRole)
- return m_kbm.text();
-
- if (role == Qt::DecorationRole)
- {
- // NOTE
- // this should be:
- // return KIcon(m_kbm.icon());
- // but I cannot let it work :(
- // I really cannot understand how let this work properly...
- if (m_kbm.isGroup() || m_kbm.isSeparator())
- return KIcon(m_kbm.icon());
- else
- return IconManager::self()->iconForUrl(KUrl(m_kbm.url()));
- }
-
- if (role == Qt::UserRole)
- return m_kbm.url();
-
- if (role == Qt::ToolTipRole)
- {
- QString tooltip = m_kbm.fullText();
- if (m_kbm.isGroup())
- tooltip += i18ncp("%1=Number of items in bookmark folder", " (1 item)", " (%1 items)", childCount());
-
- QString url = m_kbm.url().url();
- if (!url.isEmpty())
- {
- if (!tooltip.isEmpty())
- tooltip += '\n';
- tooltip += url;
- }
-
- if (!m_kbm.description().isEmpty())
- {
- if (!tooltip.isEmpty())
- tooltip += '\n';
- tooltip += m_kbm.description();
- }
-
- return tooltip;
- }
-
- return QVariant();
-}
-
-
-int BtmItem::row() const
-{
- if (m_parent)
- return m_parent->m_children.indexOf(const_cast< BtmItem* >(this));
- return 0;
-}
-
-
-int BtmItem::childCount() const
-{
- return m_children.count();
+ if (bookmarksFile->open(QIODevice::ReadOnly | QIODevice::Text)) {
+ if (path.endsWith(".xbel")) { readFns[Formats::FormatXbel](bookmarksFile, rootItem); }
+ bookmarksFile->close();
+ }
}
-
-BtmItem* BtmItem::child(int n)
+BookmarkModel::~BookmarkModel()
{
- Q_ASSERT(n >= 0);
- Q_ASSERT(n < childCount());
+ save(); // save on exit
+ bookmarksFile->flush();
- return m_children.at(n);
+ delete rootItem;
+ delete bookmarksFile;
}
-
-BtmItem* BtmItem::parent() const
+void BookmarkModel::load(const QIODevice *buffer)
{
- return m_parent;
+ if (buffer->isOpen() && buffer->isReadable()) { readFns[Formats::FormatXbel](buffer, rootItem); }
}
-
-void BtmItem::appendChild(BtmItem *child)
+QVariant BookmarkModel::headerData(int section, Qt::Orientation, int role) const
{
- if (!child)
- return;
-
- child->m_parent = this;
- m_children << child;
+ if (role != Qt::DisplayRole) return {};
+ return rootItem->data(static_cast<BookmarksTreeItem::Attributes>(section));
}
-
-void BtmItem::clear()
+QVariant BookmarkModel::data(const QModelIndex &index, int role) const
{
- qDeleteAll(m_children);
- m_children.clear();
+ if (!index.isValid()) return {};
+
+ auto *it = item(index);
+
+ // find item Alias points to
+ if (it->type() == BookmarksTreeItem::Alias) {
+ // find the item this is an alias of
+ auto *child = rootItem->findChild(it->data(BookmarksTreeItem::Href).toString());
+ if (child == nullptr) return {};
+ it = child;
+ }
+
+ switch (role) {
+ case Qt::DecorationRole:
+ if (index.column() == 0) return it->icon();
+ break;
+ case Qt::ToolTipRole:
+ return it->tooltip();
+ case Qt::DisplayRole:
+ case Qt::EditRole:
+ if (index.column() == 0) return it->data(BookmarksTreeItem::Title);
+ if (index.column() == 1) return it->data(BookmarksTreeItem::Href);
+ break;
+ case CompletionMatchingRole:
+ if (it->type() == BookmarksTreeItem::Bookmark) return it->data(BookmarksTreeItem::Href);
+ return it->data(BookmarksTreeItem::Title);
+ default:
+ break;
+ }
+
+ return {};
}
-KBookmark BtmItem::getBkm() const
+bool BookmarkModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
- return m_kbm;
-}
+ if (!index.isValid()) return false;
+ bool success = false;
-// -------------------------------------------------------------------------------------
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ auto *item = static_cast<BookmarksTreeItem *>(index.internalPointer());
+ success = item->setData(static_cast<BookmarksTreeItem::Attributes>(index.column()), value);
+ }
+ if (success) {
+ emit dataChanged(index, index, {role});
+ m_isModified = true;
+ }
-BookmarksTreeModel::BookmarksTreeModel(QObject *parent)
- : QAbstractItemModel(parent)
- , m_root(0)
-{
- resetModel();
- connect(BookmarkManager::self()->manager(), SIGNAL(changed(QString,QString)),
- this, SLOT(bookmarksChanged(QString)));
+ return success;
}
-
-BookmarksTreeModel::~BookmarksTreeModel()
+Qt::ItemFlags BookmarkModel::flags(const QModelIndex &index) const
{
- delete m_root;
+ switch (item(index)->type()) {
+ case BookmarksTreeItem::Root:
+ return QAbstractItemModel::flags(index) /*| Qt::ItemIsDragEnabled*/ | Qt::ItemIsDropEnabled;
+ case BookmarksTreeItem::Folder:
+ return QAbstractItemModel::flags(index) | (index.column() == 0 ? Qt::ItemIsEditable : Qt::NoItemFlags) |
+ Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
+ case BookmarksTreeItem::Bookmark:
+ return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren;
+ case BookmarksTreeItem::Separator:
+ return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren;
+ case BookmarksTreeItem::Alias:
+ // TODO find aliased item and return its flags
+ return QAbstractItemModel::flags(index);
+ }
+
+ __builtin_unreachable();
}
-
-int BookmarksTreeModel::rowCount(const QModelIndex &parent) const
+int BookmarkModel::rowCount(const QModelIndex &index) const
{
- BtmItem *parentItem = 0;
- if (!parent.isValid())
- {
- parentItem = m_root;
- }
- else
- {
- parentItem = static_cast<BtmItem*>(parent.internalPointer());
- }
-
- return parentItem->childCount();
+ if (index.column() > 0) return 0;
+ return static_cast<int>(item(index)->childCount());
}
-
-int BookmarksTreeModel::columnCount(const QModelIndex& /*parent*/) const
+QModelIndex BookmarkModel::appendItem(BookmarksTreeItem::Types type, BookmarksTreeItem::Attributes_t data,
+ const QModelIndex &parent)
{
- return 1;
+ auto *parentItem = item(parent);
+ const auto row = rowCount(parent);
+
+ beginInsertRows(parent, row, row);
+ auto *child = new BookmarksTreeItem(type, std::move(data), parentItem);
+ child->setData(BookmarksTreeItem::Added, QDateTime::currentDateTime());
+ bool success = parentItem->appendChild(child);
+ endInsertRows();
+
+ if (success) {
+ m_isModified = true;
+ return createIndex(row, 0, child);
+ }
+ else
+ return {};
}
-
-Qt::ItemFlags BookmarksTreeModel::flags(const QModelIndex &index) const
+bool BookmarkModel::removeRows(int position, int rows, const QModelIndex &parent)
{
- Qt::ItemFlags flags = QAbstractItemModel::flags(index);
-
- if (!index.isValid())
- return flags | Qt::ItemIsDropEnabled;
+ auto *parentItem = item(parent);
- flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
+ beginRemoveRows(parent, position, position + rows - 1);
+ bool success = parentItem->removeChildAt(position, rows);
+ endRemoveRows();
- if (bookmarkForIndex(index).isGroup())
- flags |= Qt::ItemIsDropEnabled;
-
- return flags;
+ if (success) m_isModified = true;
+ return success;
}
-
-QModelIndex BookmarksTreeModel::index(int row, int column, const QModelIndex &parent) const
+QModelIndex BookmarkModel::index(int row, int column, const QModelIndex &parent) const
{
- if (!hasIndex(row, column, parent))
- return QModelIndex();
-
- BtmItem *parentItem;
+ if (!this->hasIndex(row, column, parent)) return {};
- if (!parent.isValid())
- parentItem = m_root;
- else
- parentItem = static_cast<BtmItem*>(parent.internalPointer());
-
- BtmItem *childItem = parentItem->child(row);
- if (childItem)
- return createIndex(row, column, childItem);
-
- return QModelIndex();
+ BookmarksTreeItem *parentItem = item(parent);
+ BookmarksTreeItem *childItem = parentItem->child(row);
+ if (childItem) return createIndex(row, column, childItem);
+ return {};
}
-
-QModelIndex BookmarksTreeModel::parent(const QModelIndex &index) const
+QModelIndex BookmarkModel::parent(const QModelIndex &index) const
{
- if (!index.isValid())
- return QModelIndex();
-
- BtmItem *childItem = static_cast<BtmItem*>(index.internalPointer());
- BtmItem *parentItem = childItem->parent();
+ if (!index.isValid()) return {};
- if (parentItem == m_root)
- return QModelIndex();
-
- return createIndex(parentItem->row(), 0, parentItem);
-}
+ auto *childItem = static_cast<BookmarksTreeItem *>(index.internalPointer());
+ auto *parentItem = childItem->parent();
+ if (parentItem == rootItem) return {};
-QVariant BookmarksTreeModel::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- BtmItem *node = static_cast<BtmItem*>(index.internalPointer());
- if (node && node == m_root)
- {
- if (role == Qt::DisplayRole)
- return i18n("Bookmarks");
- if (role == Qt::DecorationRole)
- return KIcon("bookmarks");
- }
- else
- {
- if (node)
- return node->data(role);
- }
-
- return QVariant();
+ return createIndex(parentItem->row(), 0, parentItem);
}
-
-QStringList BookmarksTreeModel::mimeTypes() const
+QModelIndex BookmarkModel::parentFolder(const QModelIndex &index) const
{
- return QStringList(BookmarkManager::bookmark_mime_type());
-}
-
-
-bool BookmarksTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
-{
- if (action != Qt::MoveAction || !data->hasFormat(BookmarkManager::bookmark_mime_type()))
- return false;
-
- QByteArray addresses = data->data(BookmarkManager::bookmark_mime_type());
- KBookmark bookmark = BookmarkManager::self()->findByAddress(QString::fromLatin1(addresses.data()));
-
- KBookmarkGroup root;
- if (parent.isValid())
- root = bookmarkForIndex(parent).toGroup();
- else
- root = BookmarkManager::self()->rootGroup();
-
- QModelIndex destIndex = index(row, column, parent);
+ // invalid index is the root index -> return it back
+ if (!index.isValid()) return {};
- if (destIndex.isValid() && row != -1)
- {
- root.moveBookmark(bookmark, root.previous(bookmarkForIndex(destIndex)));
- }
- else
- {
- root.deleteBookmark(bookmark);
- root.addBookmark(bookmark);
- }
+ if (item(index)->type() == BookmarksTreeItem::Bookmark) { return index.parent(); }
- BookmarkManager::self()->emitChanged();
-
- return true;
+ return index;
}
-
-Qt::DropActions BookmarksTreeModel::supportedDropActions() const
+BookmarksTreeItem *BookmarkModel::item(const QModelIndex &index) const
{
- return Qt::MoveAction;
+ if (!index.isValid()) return rootItem;
+ return static_cast<BookmarksTreeItem *>(index.internalPointer());
}
+/*
+ * Drag'n'Drop implementation
+ * How drag and drop actually works: the view encodes the data of the original item (using BookmarkModel::mimeData), and
+ * then uses BookmarkModel::dropMimeData to create the new item. If successful, the old item is removed (through
+ * BookmarkModel::removeRows).
+ */
-QMimeData* BookmarksTreeModel::mimeData(const QModelIndexList &indexes) const
+QMimeData *BookmarkModel::mimeData(const QModelIndexList &indexes) const
{
- QMimeData *mimeData = new QMimeData;
-
- QByteArray address = bookmarkForIndex(indexes.first()).address().toLatin1();
- mimeData->setData(BookmarkManager::bookmark_mime_type(), address);
- bookmarkForIndex(indexes.first()).populateMimeData(mimeData);
-
- return mimeData;
+ QByteArray data;
+ QBuffer buffer(&data);
+ buffer.open(QIODevice::WriteOnly | QIODevice::Text);
+
+ QVector<const BookmarksTreeItem *> items;
+ for (const QModelIndex &index : indexes) {
+ if (index.isValid() && index.column() == 0) items.append(item(index));
+ }
+ xbel::write(&buffer, items);
+
+ auto *mimeData = new QMimeData;
+ mimeData->setData(mimeType, data);
+ return mimeData;
}
-
-void BookmarksTreeModel::bookmarksChanged(const QString &groupAddress)
+bool BookmarkModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column,
+ const QModelIndex &parent)
{
- if (groupAddress.isEmpty())
- {
- resetModel();
- }
- else
- {
- beginResetModel();
- BtmItem *node = m_root;
- QModelIndex nodeIndex;
-
- QStringList indexChain(groupAddress.split('/', QString::SkipEmptyParts));
- bool ok;
- int i;
- Q_FOREACH(const QString & sIndex, indexChain)
- {
- i = sIndex.toInt(&ok);
- if (!ok)
- break;
-
- if (i < 0 || i >= node->childCount())
- break;
-
- node = node->child(i);
- nodeIndex = index(i, 0, nodeIndex);
- }
- populate(node, BookmarkManager::self()->findByAddress(groupAddress).toGroup());
- endResetModel();
- }
-
- emit bookmarksUpdated();
-}
+ if (action == Qt::IgnoreAction) return true;
+ if (action != Qt::MoveAction) return false; // we only implement MoveAction's
+ if (!mimeData->hasFormat(mimeType) || column > 0) return false;
+ auto data = mimeData->data(mimeType);
+ QBuffer buffer(&data);
+ buffer.open(QIODevice::ReadOnly | QIODevice::Text);
-void BookmarksTreeModel::resetModel()
-{
- setRoot(BookmarkManager::self()->rootGroup());
-}
+ auto *fake_root = new BookmarksTreeItem(BookmarksTreeItem::Root, {}, nullptr);
+ xbel::read(&buffer, fake_root);
+ const auto childCount = static_cast<int>(fake_root->childCount());
+ auto *parentItem = item(parent);
-void BookmarksTreeModel::setRoot(KBookmarkGroup bmg)
-{
- beginResetModel();
- delete m_root;
- m_root = new BtmItem(KBookmark());
- populate(m_root, bmg);
- endResetModel();
-}
+ beginInsertRows(parent, row, row + childCount - 1);
+ for (int i = 0; i < childCount; ++i) parentItem->insertChild(row + i, fake_root->takeChild(i));
+ endInsertRows();
+ delete fake_root;
-void BookmarksTreeModel::populate(BtmItem *node, KBookmarkGroup bmg)
-{
- node->clear();
-
- if (bmg.isNull())
- return;
-
- KBookmark bm = bmg.first();
- while (!bm.isNull())
- {
- BtmItem *newChild = new BtmItem(bm);
- if (bm.isGroup())
- populate(newChild, bm.toGroup());
-
- node->appendChild(newChild);
- bm = bmg.next(bm);
- }
+ m_isModified = true;
+ return true;
}
-
-KBookmark BookmarksTreeModel::bookmarkForIndex(const QModelIndex &index) const
+void BookmarkModel::save()
{
- return static_cast<BtmItem*>(index.internalPointer())->getBkm();
+ if (m_isModified) {
+ bookmarksFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
+ m_isModified = !writeFns[Formats::FormatXbel](bookmarksFile, {rootItem});
+ bookmarksFile->close();
+ }
}
diff --git a/src/bookmarks/bookmarkstreemodel.h b/src/bookmarks/bookmarkstreemodel.h
deleted file mode 100644
index d12c898d..00000000
--- a/src/bookmarks/bookmarkstreemodel.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/* ============================================================
-*
-* This file is a part of the rekonq project
-*
-* Copyright (C) 2009 by Nils Weigel <nehlsen at gmail dot com>
-* Copyright (C) 2010-2013 by Andrea Diamantini <adjam7 at gmail dot com>
-*
-*
-* This program is free software; you can redistribute it and/or
-* modify it under the terms of the GNU General Public License as
-* published by the Free Software Foundation; either version 2 of
-* the License or (at your option) version 3 or any later version
-* accepted by the membership of KDE e.V. (or its successor approved
-* by the membership of KDE e.V.), which shall act as a proxy
-* defined in Section 14 of version 3 of the license.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* ============================================================ */
-
-
-#ifndef BOOKMARKS_TREE_MODEL_H
-#define BOOKMARKS_TREE_MODEL_H
-
-
-// Rekonq Includes
-#include "rekonq_defines.h"
-
-// KDE includes
-#include <KBookmark>
-
-// Qt Includes
-#include <QtCore/QAbstractItemModel>
-
-
-class BtmItem
-{
-public:
- BtmItem(const KBookmark &bm);
- ~BtmItem();
-
- QVariant data(int role = Qt::DisplayRole) const;
- int row() const;
- int childCount() const;
- BtmItem* child(int n);
- BtmItem* parent() const;
- void appendChild(BtmItem *child);
- void clear();
- KBookmark getBkm() const;
-
-private:
- BtmItem *m_parent;
- QList< BtmItem* > m_children;
- KBookmark m_kbm;
-};
-
-
-// -------------------------------------------------------------------------------------------------
-
-
-class REKONQ_TESTS_EXPORT BookmarksTreeModel : public QAbstractItemModel
-{
- Q_OBJECT
-
-public:
- explicit BookmarksTreeModel(QObject *parent = 0);
- virtual ~BookmarksTreeModel();
-
- /**
- * @return number of rows under the given parent.
- */
- virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
- /**
- * @return number of columns (always 1).
- */
- virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
-
- virtual Qt::ItemFlags flags(const QModelIndex &index) const;
-
- /**
- * @return index in the model specified by the given row, column and parent.
- */
- virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
- /**
- * @return parent of the given index.
- */
- virtual QModelIndex parent(const QModelIndex &index) const;
- virtual QVariant data(const QModelIndex &index, int role) const;
-
- virtual QStringList mimeTypes() const;
- virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
- virtual Qt::DropActions supportedDropActions() const;
- virtual QMimeData *mimeData(const QModelIndexList &indexes) const;
-
-private Q_SLOTS:
- void bookmarksChanged(const QString &groupAddress);
-
-Q_SIGNALS:
- void bookmarksUpdated();
-
-private:
- void resetModel();
- void setRoot(KBookmarkGroup bmg);
- void populate(BtmItem *node, KBookmarkGroup bmg);
- KBookmark bookmarkForIndex(const QModelIndex &index) const;
-
- BtmItem *m_root;
-};
-
-#endif // BOOKMARKS_TREE_MODEL_H
diff --git a/src/bookmarks/bookmarkstreemodel.hpp b/src/bookmarks/bookmarkstreemodel.hpp
new file mode 100644
index 00000000..ace596b8
--- /dev/null
+++ b/src/bookmarks/bookmarkstreemodel.hpp
@@ -0,0 +1,59 @@
+/* ============================================================
+ * rekonq
+ * ============================================================
+ * SPDX-License-Identifier: GPL-3.0-only
+ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net>
+ * ============================================================
+ * Description: rekonq bookmarks model
+ * ============================================================ */
+
+#pragma once
+
+#include "bookmarkstreeitem.hpp"
+#include <QAbstractItemModel>
+
+class QFile;
+class BookmarkModel : public QAbstractItemModel {
+ Q_OBJECT
+
+public:
+ enum Roles { CompletionMatchingRole = Qt::UserRole + 1 };
+
+ explicit BookmarkModel(const QString &path, QObject *parent = nullptr);
+ ~BookmarkModel() override;
+
+ void load(const QIODevice *buffer);
+
+ [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+ [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+ [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+ [[nodiscard]] int rowCount(const QModelIndex &index) const override;
+ bool removeRows(int position, int rows, const QModelIndex &parent) override;
+ [[nodiscard]] int columnCount(const QModelIndex &) const override { return 2; }
+
+ [[nodiscard]] Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; }
+ [[nodiscard]] QStringList mimeTypes() const override { return {mimeType}; }
+ [[nodiscard]] QMimeData *mimeData(const QModelIndexList &indexes) const override;
+ bool dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column,
+ const QModelIndex &parent) override;
+
+ [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent) const override;
+ [[nodiscard]] QModelIndex parent(const QModelIndex &index) const override;
+ [[nodiscard]] QModelIndex parentFolder(const QModelIndex &index) const;
+
+ [[nodiscard]] BookmarksTreeItem *item(const QModelIndex &index) const;
+ QModelIndex appendItem(BookmarksTreeItem::Types type, BookmarksTreeItem::Attributes_t data,
+ const QModelIndex &parent);
+
+public slots:
+ void save();
+
+private:
+ const QLatin1String mimeType = QLatin1String("application/xbel");
+
+ BookmarksTreeItem *rootItem;
+ bool m_isModified = false;
+ QFile *bookmarksFile;
+};
diff --git a/src/bookmarks/test/xbel-1.0.dtd b/src/bookmarks/test/xbel-1.0.dtd
new file mode 100644
index 00000000..59013c22
--- /dev/null
+++ b/src/bookmarks/test/xbel-1.0.dtd
@@ -0,0 +1,94 @@
+<!-- This is the XML Bookmarks Exchange Language, version 1.0. It
+ should be used with the formal public identifier:
+
+ +//IDN python.org//DTD XML Bookmark Exchange Language 1.0//EN//XML
+
+ One valid system identifier at which this DTD will remain
+ available is:
+
+ http://pyxml.sourceforge.net/topics/dtds/xbel-1.0.dtd
+
+ More information on the DTD, including reference
+ documentation, is available at:
+
+ http://www.python.org/topics/xml/xbel/
+
+ Attributes which take date/time values should encode the value
+ according to the W3C NOTE on date/time formats:
+
+ http://www.w3.org/TR/NOTE-datetime
+ -->
+
+
+<!-- Customization entities. Define these before "including" this DTD
+ to create "subclassed" DTDs.
+ -->
+<!ENTITY % local.node.att "">
+<!ENTITY % local.url.att "">
+<!ENTITY % local.nodes.mix "">
+
+<!ENTITY % node.att "id ID #IMPLIED
+ added CDATA #IMPLIED
+ %local.node.att;">
+
+<!ENTITY % url.att "href CDATA #REQUIRED
+ visited CDATA #IMPLIED
+ modified CDATA #IMPLIED
+ %local.url.att;">
+
+<!ENTITY % nodes.mix "bookmark|folder|alias|separator
+ %local.nodes.mix;">
+
+
+<!ELEMENT xbel (title?, info?, desc?, (%nodes.mix;)*)>
+<!ATTLIST xbel
+ %node.att;
+ version CDATA #FIXED "1.0"
+>
+<!ELEMENT title (#PCDATA)>
+
+<!--=================== Info ======================================-->
+
+<!ELEMENT info (metadata+)>
+
+<!ELEMENT metadata EMPTY>
+<!ATTLIST metadata
+ owner CDATA #REQUIRED
+>
+
+<!--=================== Folder ====================================-->
+
+<!ELEMENT folder (title?, info?, desc?, (%nodes.mix;)*)>
+<!ATTLIST folder
+ %node.att;
+ folded (yes|no) 'yes'
+>
+
+<!--=================== Bookmark ==================================-->
+
+<!ELEMENT bookmark (title?, info?, desc?)>
+<!ATTLIST bookmark
+ %node.att;
+ %url.att;
+>
+
+<!ELEMENT desc (#PCDATA)>
+
+<!--=================== Separator =================================-->
+
+<!ELEMENT separator EMPTY>
+
+<!--=================== Alias =====================================-->
+
+<!-- <alias> elements correspond to Netscape bookmark aliases. The
+ required "ref" attribute must refer to a <bookmark> or <folder>
+ element. Note that MSIE aliases can refer to folders, so that is
+ supported in XBEL. Applications must be careful about traversing
+ aliases to folders to avoid improper recursion through circular
+ data structures.
+ -->
+
+<!ELEMENT alias EMPTY>
+<!ATTLIST alias
+ ref IDREF #REQUIRED
+>
diff --git a/src/bookmarks/test/xbel.cpp b/src/bookmarks/test/xbel.cpp
new file mode 100644
index 00000000..a836dd9a
--- /dev/null
+++ b/src/bookmarks/test/xbel.cpp
@@ -0,0 +1,163 @@
+#include "bookmarks/bookmarkstreeformats.hpp"
+#include "bookmarks/bookmarkstreeitem.hpp"
+#include <QBuffer>
+#include <QDateTime>
+#include <gtest/gtest.h>
+
+// clazy:excludeall=non-pod-global-static
+
+constexpr auto *title = "Test Title";
+constexpr auto *desc = "Test Description";
+
+constexpr auto *id = "id";
+constexpr auto *added = "1970-01-01T12:00:00Z";
+
+constexpr auto *href = "https://some.example.com";
+constexpr auto *visited = "1970-01-01T12:00:00Z";
+constexpr auto *modified = "1970-01-01T12:00:00Z";
+
+auto *newBookmarksTreeItem()
+{
+ // Attributes: Title, Href, Description, Id, Added, Visited, Modified
+ auto *item = new BookmarksTreeItem(BookmarksTreeItem::Bookmark,
+ {title, QUrl::fromUserInput(href), QDateTime::fromString(added, Qt::ISODate),
+ QDateTime::fromString(visited, Qt::ISODate),
+ QDateTime::fromString(modified, Qt::ISODate), desc, id},
+ nullptr);
+ item->metadata = QStringList{"alpha", "beta", "gamma"};
+ return item;
+}
+
+bool operator==(const BookmarksTreeItem &lhs, const BookmarksTreeItem &rhs)
+{
+ EXPECT_EQ(lhs.type(), rhs.type());
+ EXPECT_EQ(lhs.isExpanded(), rhs.isExpanded());
+
+ for (int i = 0; i < BookmarksTreeItem::AttributeCount; ++i)
+ EXPECT_TRUE(lhs.data(static_cast<BookmarksTreeItem::Attributes>(i)) ==
+ rhs.data(static_cast<BookmarksTreeItem::Attributes>(i)));
+
+ EXPECT_EQ(lhs.metadata, rhs.metadata);
+ EXPECT_EQ(lhs.childCount(), rhs.childCount());
+ for (int i = 0; i < lhs.childCount(); ++i) EXPECT_EQ(*lhs.child(i), *rhs.child(i));
+
+ return true;
+}
+
+[[nodiscard]] bool encode(const BookmarksTreeItem *root, QByteArray &data)
+{
+ QBuffer buffer(&data);
+ if (!buffer.open(QIODevice::WriteOnly | QIODevice::Text)) return false;
+ return xbel::write(&buffer, {root});
+}
+
+[[nodiscard]] bool decode(BookmarksTreeItem *root, QByteArray &data)
+{
+ QBuffer buffer(&data);
+ if (!buffer.open(QIODevice::ReadOnly | QIODevice::Text)) return false;
+ return xbel::read(&buffer, root);
+}
+
+namespace {
+TEST(XbelFormat, RootEncodeDecode)
+{
+ auto *original = new BookmarksTreeItem(BookmarksTreeItem::Root, {}, nullptr);
+ original->metadata = QStringList{"one", "two", "three"}; // info
+ original->setData(BookmarksTreeItem::Description, "xbel root item description"); // desc
+ original->setData(BookmarksTreeItem::Id, "root"); // node.att.id
+ original->setData(BookmarksTreeItem::Added, QDateTime::fromString(QDateTime::currentDateTime().toString(Qt::ISODate),
+ Qt::ISODate)); // node.att.added
+
+ QByteArray encdodedData;
+ EXPECT_TRUE(encode(original, encdodedData));
+
+ auto *root = new BookmarksTreeItem(BookmarksTreeItem::Root, {}, nullptr);
+ EXPECT_TRUE(decode(root, encdodedData));
+
+ EXPECT_EQ(*original, *root);
+
+ // cleanuo
+ delete original;
+ delete root;
+}
+
+TEST(XbelFormat, FolderEncodeDecode)
+{
+ QByteArray encodedData;
+
+ auto *folder = new BookmarksTreeItem(BookmarksTreeItem::Folder, {.title = "Folder"}, nullptr);
+ folder->appendChild(newBookmarksTreeItem());
+ folder->appendChild(newBookmarksTreeItem());
+ EXPECT_TRUE(encode(folder, encodedData));
+
+ auto *root = new BookmarksTreeItem(BookmarksTreeItem::Root, {}, nullptr);
+ EXPECT_TRUE(decode(root, encodedData));
+
+ EXPECT_EQ(root->childCount(), 1);
+ auto *item = root->child(0);
+ EXPECT_TRUE(*folder == *item);
+
+ // cleanup
+ delete folder;
+ delete root;
+}
+
+TEST(XbelFormat, BookmarkEncodeDecode)
+{
+ QByteArray encodedData;
+
+ auto *original = newBookmarksTreeItem();
+ EXPECT_TRUE(encode(original, encodedData));
+
+ auto *root = new BookmarksTreeItem(BookmarksTreeItem::Root, {}, nullptr);
+ EXPECT_TRUE(decode(root, encodedData));
+
+ EXPECT_EQ(root->childCount(), 1);
+ auto *item = root->child(0);
+ EXPECT_EQ(*original, *item);
+
+ // cleanup
+ delete original;
+ delete root;
+}
+
+TEST(XbelFormat, SeparatorEncodeDecode)
+{
+ QByteArray encodedData;
+
+ auto *original = new BookmarksTreeItem(BookmarksTreeItem::Separator, {}, nullptr);
+ EXPECT_TRUE(encode(original, encodedData));
+
+ auto *root = new BookmarksTreeItem(BookmarksTreeItem::Root, {}, nullptr);
+ EXPECT_TRUE(decode(root, encodedData));
+
+ EXPECT_EQ(root->childCount(), 1);
+ auto *item = root->child(0);
+ EXPECT_EQ(*original, *item);
+
+ // cleanup
+ delete original;
+ delete root;
+}
+
+TEST(XbelFormat, AliasEncodeDecode)
+{
+ QByteArray encodedData;
+
+ auto *original = new BookmarksTreeItem(BookmarksTreeItem::Alias, {}, nullptr);
+ original->setData(BookmarksTreeItem::Id, "aliased_id");
+ EXPECT_TRUE(encode(original, encodedData));
+
+ auto *root = new BookmarksTreeItem(BookmarksTreeItem::Root, {}, nullptr);
+ EXPECT_TRUE(decode(root, encodedData));
+
+ EXPECT_EQ(root->childCount(), 1);
+ auto *item = root->child(0);
+ EXPECT_EQ(*original, *item);
+
+ // cleanup
+ delete original;
+ delete root;
+}
+
+} // namespace