From c2d18d73b0e1a74525ec0cda36a2e2e7e5b4ff4c Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 14 Aug 2022 18:47:37 +0300 Subject: Import BookmarkModel from poi --- src/bookmarks/CMakeLists.txt | 22 + src/bookmarks/bookmarkmanager.cpp | 81 +--- src/bookmarks/bookmarkmanager.h | 174 -------- src/bookmarks/bookmarkmanager.hpp | 123 ++++++ src/bookmarks/bookmarkowner.h | 16 +- src/bookmarks/bookmarkscontextmenu.h | 4 - src/bookmarks/bookmarksmenu.h | 8 +- src/bookmarks/bookmarkstoolbar.h | 17 +- src/bookmarks/bookmarkstreeformat_xbel.h | 67 +++ src/bookmarks/bookmarkstreeformat_xbel_read.cpp | 184 ++++++++ src/bookmarks/bookmarkstreeformat_xbel_write.cpp | 142 +++++++ src/bookmarks/bookmarkstreeformats.hpp | 21 + src/bookmarks/bookmarkstreeitem.cpp | 109 +++++ src/bookmarks/bookmarkstreeitem.hpp | 108 +++++ src/bookmarks/bookmarkstreemodel.cpp | 518 ++++++++--------------- src/bookmarks/bookmarkstreemodel.h | 116 ----- src/bookmarks/bookmarkstreemodel.hpp | 59 +++ src/bookmarks/test/xbel-1.0.dtd | 94 ++++ src/bookmarks/test/xbel.cpp | 163 +++++++ 19 files changed, 1307 insertions(+), 719 deletions(-) create mode 100644 src/bookmarks/CMakeLists.txt delete mode 100644 src/bookmarks/bookmarkmanager.h create mode 100644 src/bookmarks/bookmarkmanager.hpp create mode 100644 src/bookmarks/bookmarkstreeformat_xbel.h create mode 100644 src/bookmarks/bookmarkstreeformat_xbel_read.cpp create mode 100644 src/bookmarks/bookmarkstreeformat_xbel_write.cpp create mode 100644 src/bookmarks/bookmarkstreeformats.hpp create mode 100644 src/bookmarks/bookmarkstreeitem.cpp create mode 100644 src/bookmarks/bookmarkstreeitem.hpp delete mode 100644 src/bookmarks/bookmarkstreemodel.h create mode 100644 src/bookmarks/bookmarkstreemodel.hpp create mode 100644 src/bookmarks/test/xbel-1.0.dtd create mode 100644 src/bookmarks/test/xbel.cpp (limited to 'src/bookmarks') 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 -* Copyright (C) 2009 by Paweł Prażak -* Copyright (C) 2009-2010 by Lionel Chauvin -* Copyright (C) 2010 by Yoann Laissus -* -* -* 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 . -* -* ============================================================ */ - + * The rekonq project + * ============================================================ + * SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (C) 2008-2012 by Andrea Diamantini + * Copyright (C) 2009 by Paweł Prażak + * Copyright (C) 2009-2010 by Lionel Chauvin + * Copyright (C) 2010 by Yoann Laissus + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2022 aqua + * ============================================================ + * 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 -#include - -// Qt Includes -#include - +#include +#include +#include // ---------------------------------------------------------------------------------------------- - -QWeakPointer BookmarkManager::s_bookmarkManager; - +static std::unique_ptr 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 -* Copyright (C) 2009 by Paweł Prażak -* Copyright (C) 2009-2010 by Lionel Chauvin -* Copyright (C) 2010 by Yoann Laissus -* -* -* 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 . -* -* ============================================================ */ - - -#ifndef BOOKMARK_MANAGER_H -#define BOOKMARK_MANAGER_H - - -// Rekonq Includes -#include "rekonq_defines.h" - -// KDE Includes -#include -#include - -// Qt Includes -#include -#include - -// 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 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 *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 m_bookmarkToolBars; - - static QWeakPointer 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 + * Copyright (C) 2009 by Paweł Prażak + * Copyright (C) 2009-2010 by Lionel Chauvin + * Copyright (C) 2010 by Yoann Laissus + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2022 aqua + * ============================================================ + * Description: rekonq bookmarks system interface + * ============================================================ */ + +#pragma once + +#include "bookmarkstreemodel.hpp" +#include "rekonq.hpp" +#include + +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 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 *list, const KBookmark &bookmark, const QString &text); + KBookmark bookmarkForUrl(const KBookmark &bookmark, const KUrl &url); + void copyBookmarkGroup(const KBookmarkGroup &groupToCopy, KBookmarkGroup destGroup); + + QList 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 -#include - +#include "rekonq.hpp" +#include /** * 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 - // 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 - +#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 - -// Forward Declarations -class KMenu; - +#include "rekonq.hpp" +#include /** * 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 + * ============================================================ + * Description: rekonq bookmarks model + * ============================================================ */ + +#pragma once + +#include + +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 &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 + * ============================================================ + * Description: rekonq bookmarks model + * ============================================================ */ + +#include "bookmarkstreeformat_xbel.h" +#include "bookmarkstreeitem.hpp" +#include +#include + +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 + * ============================================================ + * Description: rekonq bookmarks model + * ============================================================ */ + +#include "bookmarkstreeformat_xbel.h" +#include "bookmarkstreeitem.hpp" +#include + +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 &items) +{ + QXmlStreamWriter xmlWriter(device); + xmlWriter.setAutoFormatting(true); + + xmlWriter.writeStartDocument(); + xmlWriter.writeDTD(""); + + 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 + * ============================================================ + * Description: rekonq bookmarks model + * ============================================================ */ + +#pragma once + +#include "bookmarkstreeformat_xbel.h" +#include + +typedef bool (*t_readFn)(QIODevice *, BookmarksTreeItem *); +typedef bool (*t_writeFn)(QIODevice *, const QVector &); + +enum Formats { FormatXbel = 0 }; + +constexpr std::array readFns{&xbel::read}; +constexpr std::array 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 + * ============================================================ + * 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("

%1

").arg(m_data.title); + if (const auto href = m_data.href.toString(); !href.isEmpty()) + msg += QString("

%2

").arg(m_data.href.toString()); + return msg; +} + +int BookmarksTreeItem::row() const +{ + if (m_parent) return static_cast(m_parent->m_children.indexOf(const_cast(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 + * ============================================================ + * Description: rekonq bookmarks model + * ============================================================ */ + +#pragma once + +#include "rekonq.hpp" +#include +#include +#include +#include +#include +#include + +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 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 -* Copyright (C) 2010-2013 by Andrea Diamantini -* -* -* 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 . -* -* ============================================================ */ - - -// Self Includes -#include "bookmarkstreemodel.h" -#include "bookmarkstreemodel.moc" - -// Local Includes -#include "bookmarkmanager.h" -#include "iconmanager.h" - -// KDE Includes -#include -#include -#include - -// Qt Includes -#include - - -BtmItem::BtmItem(const KBookmark &bm) - : m_parent(0) - , m_kbm(bm) + * rekonq + * ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2022 aqua + * ============================================================ + * Description: rekonq bookmarks model + * ============================================================ */ + +#include "bookmarkstreemodel.hpp" +#include "bookmarkstreeformats.hpp" +#include +#include +#include +#include + +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(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(index.internalPointer()); + success = item->setData(static_cast(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(parent.internalPointer()); - } - - return parentItem->childCount(); + if (index.column() > 0) return 0; + return static_cast(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(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(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(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(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(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 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(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(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 -* Copyright (C) 2010-2013 by Andrea Diamantini -* -* -* 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 . -* -* ============================================================ */ - - -#ifndef BOOKMARKS_TREE_MODEL_H -#define BOOKMARKS_TREE_MODEL_H - - -// Rekonq Includes -#include "rekonq_defines.h" - -// KDE includes -#include - -// Qt Includes -#include - - -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 + * ============================================================ + * Description: rekonq bookmarks model + * ============================================================ */ + +#pragma once + +#include "bookmarkstreeitem.hpp" +#include + +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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 +#include +#include + +// 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(i)) == + rhs.data(static_cast(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 -- cgit v1.2.1