aboutsummaryrefslogtreecommitdiff
path: root/lib/bookmarks/bookmarkmodel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bookmarks/bookmarkmodel.cpp')
-rw-r--r--lib/bookmarks/bookmarkmodel.cpp356
1 files changed, 356 insertions, 0 deletions
diff --git a/lib/bookmarks/bookmarkmodel.cpp b/lib/bookmarks/bookmarkmodel.cpp
new file mode 100644
index 0000000..895b178
--- /dev/null
+++ b/lib/bookmarks/bookmarkmodel.cpp
@@ -0,0 +1,356 @@
+/*
+ * This file is part of smolbote. It's copyrighted by the contributors recorded
+ * in the version control history of the file, available from its original
+ * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote
+ *
+ * SPDX-License-Identifier: GPL-3.0
+ */
+
+#include "bookmarkmodel.h"
+#include "bookmarkitem.h"
+#include "formats/xbel.h"
+#include <QBuffer>
+#include <QMimeData>
+#include <QRegularExpression>
+
+BookmarkModel::BookmarkModel(QObject *parent)
+ : QAbstractItemModel(parent)
+{
+ rootItem = new BookmarkItem({ tr("Title"), tr("Address") }, BookmarkItem::Root, nullptr);
+}
+
+BookmarkModel::~BookmarkModel()
+{
+ delete rootItem;
+}
+
+QVariant BookmarkModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
+ return rootItem->data(static_cast<BookmarkItem::Fields>(section));
+
+ return QVariant();
+}
+
+QVariant BookmarkModel::data(const QModelIndex &index, int role) const
+{
+ if(!index.isValid())
+ return QVariant();
+
+ if(role == Qt::DecorationRole && index.column() == 0)
+ return static_cast<BookmarkItem *>(index.internalPointer())->icon();
+
+ else if(role == Qt::ToolTipRole)
+ return static_cast<BookmarkItem *>(index.internalPointer())->tooltip();
+
+ else if(role == Qt::DisplayRole)
+ return static_cast<BookmarkItem *>(index.internalPointer())->data(static_cast<BookmarkItem::Fields>(index.column()));
+
+ else
+ return QVariant();
+}
+
+QVariant BookmarkModel::data(const QModelIndex &index, int column, int role) const
+{
+ if(!index.isValid())
+ return QVariant();
+
+ if(role == Qt::DisplayRole)
+ return static_cast<BookmarkItem *>(index.internalPointer())->data(static_cast<BookmarkItem::Fields>(column));
+
+ return QVariant();
+}
+
+bool BookmarkModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if(!index.isValid())
+ return false;
+
+ bool success = false;
+
+ if(role == Qt::DisplayRole) {
+ success = static_cast<BookmarkItem *>(index.internalPointer())->setData(static_cast<BookmarkItem::Fields>(index.column()), value);
+ }
+
+ if(success) {
+ emit dataChanged(index, index, { role });
+ m_isModified = true;
+ }
+ return success;
+}
+
+bool BookmarkModel::setData(const QModelIndex &index, const QVariant &value, BookmarkItem::Fields column, int role)
+{
+ if(!index.isValid() || role != Qt::DisplayRole)
+ return false;
+
+ bool success = static_cast<BookmarkItem *>(index.internalPointer())->setData(column, value);
+ if(success) {
+ emit dataChanged(index, index, { role });
+ m_isModified = true;
+ }
+ return success;
+}
+
+Qt::ItemFlags BookmarkModel::flags(const QModelIndex &index) const
+{
+ if(getItem(index)->type() == BookmarkItem::Folder)
+ return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
+ else
+ return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren;
+}
+
+bool BookmarkModel::isItemExpanded(const QModelIndex &index) const
+{
+ if(!index.isValid())
+ return false;
+
+ return static_cast<BookmarkItem *>(index.internalPointer())->isExpanded();
+}
+
+void BookmarkModel::setItemExpanded(const QModelIndex &index, bool expanded)
+{
+ BookmarkItem *item = getItem(index);
+ if(item->type() == BookmarkItem::Folder) {
+ item->setExpanded(expanded);
+ m_isModified = true;
+ }
+}
+
+int BookmarkModel::rowCount(const QModelIndex &index) const
+{
+ if(index.column() > 0)
+ return 0;
+
+ return getItem(index)->childCount();
+}
+
+QModelIndex BookmarkModel::appendBookmark(const QString &title, const QString &url, const QModelIndex &parent)
+{
+ auto *parentItem = getItem(parent);
+
+ int row = parentItem->childCount();
+ beginInsertRows(parent, row, row);
+ auto *childItem = new BookmarkItem({ title, url }, BookmarkItem::Bookmark, parentItem);
+ parentItem->appendChild(childItem);
+ endInsertRows();
+
+ m_isModified = true;
+ return createIndex(row, 0, childItem);
+}
+
+QModelIndex BookmarkModel::appendFolder(const QString &title, const QModelIndex &parent)
+{
+ auto *parentItem = getItem(parent);
+ const int row = parentItem->childCount();
+
+ beginInsertRows(parent, row, row);
+ auto *childItem = new BookmarkItem({ title }, BookmarkItem::Folder, parentItem);
+ parentItem->appendChild(childItem);
+ endInsertRows();
+
+ m_isModified = true;
+ return createIndex(row, 0, childItem);
+}
+
+bool BookmarkModel::removeRows(int position, int rows, const QModelIndex &parent)
+{
+ auto *parentItem = getItem(parent);
+
+ beginRemoveRows(parent, position, position + rows - 1);
+ bool success = parentItem->removeChildAt(position, rows);
+ endRemoveRows();
+
+ if(success)
+ m_isModified = true;
+ return success;
+}
+
+int BookmarkModel::columnCount(const QModelIndex &index) const
+{
+ Q_UNUSED(index);
+ return 2;
+}
+
+QModelIndex BookmarkModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if(!this->hasIndex(row, column, parent))
+ return QModelIndex();
+
+ BookmarkItem *parentItem = getItem(parent);
+ BookmarkItem *childItem = parentItem->child(row);
+ if(childItem)
+ return createIndex(row, column, childItem);
+ else
+ return QModelIndex();
+}
+
+QModelIndex BookmarkModel::parent(const QModelIndex &index) const
+{
+ if(!index.isValid())
+ return QModelIndex();
+
+ auto *childItem = static_cast<BookmarkItem *>(index.internalPointer());
+ auto *parentItem = childItem->parent();
+
+ if(parentItem == rootItem)
+ return QModelIndex();
+
+ return createIndex(parentItem->row(), 0, parentItem);
+}
+
+QModelIndex BookmarkModel::parentFolder(const QModelIndex &index) const
+{
+ // invalid index is the root index -> return it back
+ if(!index.isValid())
+ return QModelIndex();
+
+ if(getItem(index)->type() == BookmarkItem::Bookmark) {
+ return index.parent();
+ }
+
+ return index;
+}
+
+inline bool has(const QStringList &terms, const QStringList &where)
+{
+ for(const QString &term : terms) {
+ if(where.contains(term))
+ return true;
+ }
+ return false;
+}
+
+inline QStringList searchThrough(const QString &term, const QStringList &tags, BookmarkItem *item)
+{
+ QStringList results;
+
+ for(int i = 0; i < item->childCount(); ++i) {
+ auto *child = item->child(i);
+
+ if(child->type() == BookmarkItem::Bookmark) {
+ if((!term.isEmpty() && child->data(BookmarkItem::Href).toString().contains(term)) || has(tags, child->data(BookmarkItem::Tags).toStringList()))
+ results.append(child->data(BookmarkItem::Href).toString());
+ }
+
+ else if(child->type() == BookmarkItem::Folder) {
+ if(has(tags, child->data(BookmarkItem::Tags).toStringList())) {
+
+ // append all bookmarks
+ for(int i = 0; i < child->childCount(); ++i) {
+ auto *subChild = child->child(i);
+ if(subChild->type() == BookmarkItem::Bookmark)
+ results.append(subChild->data(BookmarkItem::Href).toString());
+ }
+ }
+ results.append(searchThrough(term, tags, child));
+ }
+ }
+ return results;
+}
+
+QStringList BookmarkModel::search(const QString &term) const
+{
+ QString searchTerm = term;
+ QStringList tags;
+
+ const QRegularExpression tagRE(":\\w+\\s?", QRegularExpression::CaseInsensitiveOption);
+ auto i = tagRE.globalMatch(term);
+ while(i.hasNext()) {
+ auto match = i.next();
+ QString tag = match.captured();
+ searchTerm.remove(tag);
+ tag = tag.remove(0, 1).trimmed();
+ tags.append(tag);
+ }
+
+ return searchThrough(searchTerm, tags, rootItem);
+}
+
+BookmarkItem *BookmarkModel::getItem(const QModelIndex &index) const
+{
+ if(!index.isValid())
+ return rootItem;
+ else
+ return static_cast<BookmarkItem *>(index.internalPointer());
+}
+
+/*
+ * Drag'n'Drop implementation
+ * How drag and drop actually works: the view encodes the data of the original
+ * item (through ::mimeData), and then uses ::dropMimeData to create the new
+ * item. If successful, the old item is removed (through ::removeRows).
+ * This means that the encoding and decoding needs to be provided. In this case,
+ * this is done through xbel.
+ */
+
+Qt::DropActions BookmarkModel::supportedDropActions() const
+{
+ return Qt::MoveAction;
+}
+
+QStringList BookmarkModel::mimeTypes() const
+{
+ return { mimeType };
+}
+
+QMimeData *BookmarkModel::mimeData(const QModelIndexList &indexes) const
+{
+ auto *mimeData = new QMimeData();
+ QByteArray data;
+
+ QDataStream stream(&data, QIODevice::WriteOnly);
+ for(const QModelIndex &index : indexes) {
+ if(index.isValid() && index.column() == 0) {
+ QByteArray encodedData;
+ QBuffer buffer(&encodedData);
+ buffer.open(QIODevice::WriteOnly);
+
+ Xbel::write(&buffer, getItem(index));
+
+ stream << encodedData;
+ }
+ }
+ mimeData->setData(mimeType, data);
+ return mimeData;
+}
+bool BookmarkModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent)
+{
+ if(action == Qt::IgnoreAction)
+ return true;
+
+ if(action != Qt::MoveAction)
+ return false;
+
+ if(!mimeData->hasFormat(mimeType) || column > 0)
+ return false;
+
+ QByteArray data = mimeData->data(mimeType);
+ QDataStream stream(&data, QIODevice::ReadOnly);
+ if(stream.atEnd())
+ return false;
+
+ while(!stream.atEnd()) {
+ QByteArray encodedData;
+ stream >> encodedData;
+
+ QBuffer buffer(&encodedData);
+ buffer.open(QIODevice::ReadOnly);
+
+ auto *fakeRoot = new BookmarkItem({}, BookmarkItem::Folder, nullptr);
+ auto *parentItem = getItem(parent);
+ Xbel::read(&buffer, fakeRoot);
+
+ beginInsertRows(parent, row, row + fakeRoot->childCount() - 1);
+ for(int i = 0; i < fakeRoot->childCount(); ++i) {
+ auto *child = fakeRoot->takeChild(0, parentItem);
+ parentItem->insertChild(row, child);
+ }
+ endInsertRows();
+
+ delete fakeRoot;
+ }
+
+ m_isModified = true;
+ return true;
+}