/* ============================================================ * 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 BookmarkModel::BookmarkModel(QObject *parent) : QAbstractItemModel(parent) { rootItem = new BookmarksTreeItem(BookmarksTreeItem::Root, {.title = tr("Title"), .href = tr("Address")}, nullptr); } BookmarkModel::~BookmarkModel() { delete rootItem; } QVariant BookmarkModel::headerData(int section, Qt::Orientation, int role) const { if (role != Qt::DisplayRole) return {}; return rootItem->data(static_cast(section)); } QVariant BookmarkModel::data(const QModelIndex &index, int role) const { 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 {}; } bool BookmarkModel::setData(const QModelIndex &index, const QVariant &value, int role) { 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; } return success; } Qt::ItemFlags BookmarkModel::flags(const QModelIndex &index) const { 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 BookmarkModel::rowCount(const QModelIndex &index) const { if (index.column() > 0) return 0; return static_cast(item(index)->childCount()); } QModelIndex BookmarkModel::appendItem(BookmarksTreeItem::Types type, BookmarksTreeItem::Attributes_t data, const QModelIndex &parent) { 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 {}; } bool BookmarkModel::removeRows(int position, int rows, const QModelIndex &parent) { auto *parentItem = item(parent); beginRemoveRows(parent, position, position + rows - 1); bool success = parentItem->removeChildAt(position, rows); endRemoveRows(); if (success) m_isModified = true; return success; } QModelIndex BookmarkModel::index(int row, int column, const QModelIndex &parent) const { if (!this->hasIndex(row, column, parent)) return {}; BookmarksTreeItem *parentItem = item(parent); BookmarksTreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); return {}; } QModelIndex BookmarkModel::parent(const QModelIndex &index) const { if (!index.isValid()) return {}; auto *childItem = static_cast(index.internalPointer()); auto *parentItem = childItem->parent(); if (parentItem == rootItem) return {}; 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 {}; if (item(index)->type() == BookmarksTreeItem::Bookmark) { return index.parent(); } return index; } BookmarksTreeItem *BookmarkModel::item(const QModelIndex &index) const { 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 *BookmarkModel::mimeData(const QModelIndexList &indexes) const { 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)); } const auto write_result = xbel::write(&buffer, items); Q_ASSERT(write_result == false); auto *mimeData = new QMimeData; 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; // 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); auto *fake_root = new BookmarksTreeItem(BookmarksTreeItem::Root, {}, nullptr); const auto read_result = xbel::read(&buffer, fake_root); Q_ASSERT(read_result.isEmpty()); const auto childCount = static_cast(fake_root->childCount()); auto *parentItem = item(parent); 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; m_isModified = true; return true; } QList BookmarkModel::load(QIODevice *buffer) { return readFns[Formats::FormatXbel](buffer, rootItem); } void BookmarkModel::save(QIODevice *buffer) { if (!buffer->isOpen() || !buffer->isWritable()) return; m_isModified = !writeFns[Formats::FormatXbel](buffer, {rootItem}); }