/* * 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 #include #include #include 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(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(index.internalPointer())->icon(); else if(role == Qt::ToolTipRole) return static_cast(index.internalPointer())->tooltip(); else if(role == Qt::DisplayRole) return static_cast(index.internalPointer())->data(static_cast(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(index.internalPointer())->data(static_cast(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(index.internalPointer())->setData(static_cast(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(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 { const auto type = getItem(index)->type(); if(type == BookmarkItem::Folder || type == BookmarkItem::Root) 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(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); childItem->setData(BookmarkItem::DateAdded, QDateTime::currentDateTime()); 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); childItem->setData(BookmarkItem::DateAdded, QDateTime::currentDateTime()); 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(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(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; }