From a1d164895fac7fd705c8cb37c4c94700be32a0a2 Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Fri, 5 Oct 2018 21:07:45 +0200 Subject: bookmarks: fix new/delete buttons --- lib/bookmarks/CMakeLists.txt | 23 +- lib/bookmarks/bookmarkitem.cpp | 136 ------------ lib/bookmarks/bookmarkitem.h | 69 ------ lib/bookmarks/bookmarkmodel.cpp | 309 -------------------------- lib/bookmarks/bookmarkmodel.h | 59 ----- lib/bookmarks/bookmarkswidget.cpp | 43 +++- lib/bookmarks/bookmarkswidget.h | 3 + lib/bookmarks/forms/editbookmarkdialog.cpp | 6 +- lib/bookmarks/model/bookmarkitem.cpp | 136 ++++++++++++ lib/bookmarks/model/bookmarkitem.h | 69 ++++++ lib/bookmarks/model/bookmarkmodel.cpp | 336 +++++++++++++++++++++++++++++ lib/bookmarks/model/bookmarkmodel.h | 61 ++++++ lib/bookmarks/xbel.cpp | 2 +- 13 files changed, 658 insertions(+), 594 deletions(-) delete mode 100644 lib/bookmarks/bookmarkitem.cpp delete mode 100644 lib/bookmarks/bookmarkitem.h delete mode 100644 lib/bookmarks/bookmarkmodel.cpp delete mode 100644 lib/bookmarks/bookmarkmodel.h create mode 100644 lib/bookmarks/model/bookmarkitem.cpp create mode 100644 lib/bookmarks/model/bookmarkitem.h create mode 100644 lib/bookmarks/model/bookmarkmodel.cpp create mode 100644 lib/bookmarks/model/bookmarkmodel.h (limited to 'lib') diff --git a/lib/bookmarks/CMakeLists.txt b/lib/bookmarks/CMakeLists.txt index d8bdb2b..ebbb035 100644 --- a/lib/bookmarks/CMakeLists.txt +++ b/lib/bookmarks/CMakeLists.txt @@ -7,18 +7,19 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) add_library(bookmarks - bookmarksform.ui - bookmarkswidget.cpp - bookmarkswidget.h - # xbel - xbel.cpp - xbel.h + bookmarksform.ui + bookmarkswidget.cpp + bookmarkswidget.h + # xbel + xbel.cpp + xbel.h + + # bookmark item/model/view + model/bookmarkitem.cpp + model/bookmarkitem.h + model/bookmarkmodel.cpp + model/bookmarkmodel.h - # bookmark item/model/view - bookmarkitem.cpp - bookmarkitem.h - bookmarkmodel.cpp - bookmarkmodel.h forms/editbookmarkdialog.cpp forms/editbookmarkdialog.h forms/editbookmarkdialog.ui diff --git a/lib/bookmarks/bookmarkitem.cpp b/lib/bookmarks/bookmarkitem.cpp deleted file mode 100644 index 4715b6f..0000000 --- a/lib/bookmarks/bookmarkitem.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 "bookmarkitem.h" -#include -#include - -BookmarkItem::BookmarkItem(const QVector &data, Type type, BookmarkItem *parent) -{ - m_parentItem = parent; - - m_type = type; - if(m_type == Folder) { - m_icon.addPixmap(qApp->style()->standardPixmap(QStyle::SP_DirClosedIcon), QIcon::Normal, QIcon::Off); - m_icon.addPixmap(qApp->style()->standardPixmap(QStyle::SP_DirOpenIcon), QIcon::Normal, QIcon::On); - } else if(m_type == Bookmark) - m_icon.addPixmap(qApp->style()->standardPixmap(QStyle::SP_FileIcon)); - - m_data.resize(FieldCount); - for(int i = 0; i < FieldCount; ++i) { - m_data[i] = data.value(i, QVariant()); - } -} - -BookmarkItem::~BookmarkItem() -{ - qDeleteAll(m_children); -} - -BookmarkItem *BookmarkItem::parent() const -{ - return m_parentItem; -} - -bool BookmarkItem::appendChild(BookmarkItem *childItem) -{ - // Only folders can have children, so only append them on folders - // This way, we don't need to add checks to the other methods - if(m_type == Folder || m_type == Root) { - m_children.append(childItem); - return true; - } - - return false; -} - -bool BookmarkItem::insertChild(int position, BookmarkItem *childItem) -{ - if(m_type == Folder || m_type == Root) { - m_children.insert(position, childItem); - return true; - } - - return false; -} - -bool BookmarkItem::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; -} - -BookmarkItem *BookmarkItem::takeChild(int index, BookmarkItem *newParent) -{ - m_children[index]->m_parentItem = newParent; - return m_children.takeAt(index); -} - -BookmarkItem *BookmarkItem::child(int index) const -{ - return m_children.value(index); -} - -int BookmarkItem::childCount() const -{ - return m_children.count(); -} - -QVariant BookmarkItem::data(Fields column) const -{ - return m_data.value(column); -} - -bool BookmarkItem::setData(Fields column, const QVariant &data) -{ - if(column >= FieldCount) - return false; - - m_data[column] = data; - return true; -} - -QIcon BookmarkItem::icon() const -{ - return m_icon; -} - -bool BookmarkItem::isExpanded() const -{ - return m_isExpanded; -} - -void BookmarkItem::setExpanded(bool expanded) -{ - if(m_type == BookmarkItem::Folder) - m_isExpanded = expanded; -} - -QString BookmarkItem::tooltip() const -{ - return m_data.value(Tags).toStringList().join(", "); -} - -BookmarkItem::Type BookmarkItem::type() const -{ - return m_type; -} - -int BookmarkItem::row() const -{ - if(m_parentItem) - return m_parentItem->m_children.indexOf(const_cast(this)); - - return 0; -} diff --git a/lib/bookmarks/bookmarkitem.h b/lib/bookmarks/bookmarkitem.h deleted file mode 100644 index 8c9463f..0000000 --- a/lib/bookmarks/bookmarkitem.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 - */ - -#ifndef SMOLBOTE_BOOKMARKITEM_H -#define SMOLBOTE_BOOKMARKITEM_H - -#include -#include -#include - -class BookmarkItem -{ -public: - enum Type { - Root, - Folder, - Bookmark, - }; - - enum Fields { - Title, - Href, - Tags, - Description, - FieldCount - }; - - explicit BookmarkItem(const QVector &data, Type type, BookmarkItem *parent = nullptr); - ~BookmarkItem(); - - BookmarkItem *parent() const; - - bool appendChild(BookmarkItem *childItem); - bool insertChild(int position, BookmarkItem *childItem); - bool removeChildAt(int index, int count = 1); - BookmarkItem *takeChild(int index, BookmarkItem *newParent); - - BookmarkItem *child(int index) const; - int childCount() const; - - QVariant data(Fields column) const; - bool setData(Fields column, const QVariant &data); - - QIcon icon() const; - bool isExpanded() const; - void setExpanded(bool expanded); - - QString tooltip() const; - Type type() const; - int row() const; - -private: - QVector m_children; - BookmarkItem *m_parentItem; - - Type m_type; - QIcon m_icon; - bool m_isExpanded = false; - - // fields - QVector m_data; -}; - -#endif // SMOLBOTE_BOOKMARKITEM_H diff --git a/lib/bookmarks/bookmarkmodel.cpp b/lib/bookmarks/bookmarkmodel.cpp deleted file mode 100644 index 936176b..0000000 --- a/lib/bookmarks/bookmarkmodel.cpp +++ /dev/null @@ -1,309 +0,0 @@ -/* - * 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 "xbel.h" -#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 }); - 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 }); - 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(index.internalPointer())->isExpanded(); -} - -int BookmarkModel::rowCount(const QModelIndex &index) const -{ - if(index.column() > 0) - return 0; - - return getItem(index)->childCount(); -} - -bool BookmarkModel::appendBookmark(const QString &title, const QString &url, const QModelIndex &parent) -{ - auto *parentItem = getItem(parent); - - int row = parentItem->childCount(); - beginInsertRows(parent, row, row); - parentItem->appendChild(new BookmarkItem({ title, url }, BookmarkItem::Bookmark, parentItem)); - endInsertRows(); - return true; -} - -bool BookmarkModel::removeRows(int position, int rows, const QModelIndex &parent) -{ - qDebug("removeRows: pos=%i rows=%i", position, rows); - auto *parentItem = getItem(parent); - - beginRemoveRows(parent, position, position + rows - 1); - bool success = parentItem->removeChildAt(position, rows); - endRemoveRows(); - 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); -} - -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; - } - return true; -} diff --git a/lib/bookmarks/bookmarkmodel.h b/lib/bookmarks/bookmarkmodel.h deleted file mode 100644 index 28267aa..0000000 --- a/lib/bookmarks/bookmarkmodel.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 - */ - -#ifndef SMOLBOTE_BOOKMARKMODEL_H -#define SMOLBOTE_BOOKMARKMODEL_H - -#include "bookmarkitem.h" -#include - -class BookmarkModel : public QAbstractItemModel -{ - Q_OBJECT - -public: - explicit BookmarkModel(QObject *parent = nullptr); - ~BookmarkModel() override; - - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant data(const QModelIndex &index, int column, int role) const; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - bool setData(const QModelIndex &index, const QVariant &value, BookmarkItem::Fields column, int role); - Qt::ItemFlags flags(const QModelIndex &index) const override; - - bool isItemExpanded(const QModelIndex &index) const; - - int rowCount(const QModelIndex &index) const override; - bool appendBookmark(const QString &title, const QString &url, const QModelIndex &parent); - bool removeRows(int position, int rows, const QModelIndex &parent) override; - int columnCount(const QModelIndex &index) const override; - - Qt::DropActions supportedDropActions() const override; - QStringList mimeTypes() const override; - QMimeData *mimeData(const QModelIndexList &indexes) const override; - bool dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - - QModelIndex index(int row, int column, const QModelIndex &parent) const override; - QModelIndex parent(const QModelIndex &index) const override; - - BookmarkItem *root() - { - return rootItem; - } - - QStringList search(const QString &term) const; - -private: - const QLatin1Literal mimeType = QLatin1Literal("application/xbel"); - - BookmarkItem *getItem(const QModelIndex &index) const; - BookmarkItem *rootItem; -}; - -#endif // SMOLBOTE_BOOKMARKMODEL_H diff --git a/lib/bookmarks/bookmarkswidget.cpp b/lib/bookmarks/bookmarkswidget.cpp index 4fa42eb..1a4552a 100644 --- a/lib/bookmarks/bookmarkswidget.cpp +++ b/lib/bookmarks/bookmarkswidget.cpp @@ -7,8 +7,8 @@ */ #include "bookmarkswidget.h" -#include "bookmarkitem.h" -#include "bookmarkmodel.h" +#include "model/bookmarkitem.h" +#include "model/bookmarkmodel.h" #include "forms/editbookmarkdialog.h" #include "ui_bookmarksform.h" #include "xbel.h" @@ -56,16 +56,39 @@ BookmarksWidget::BookmarksWidget(const QString &path, QWidget *parent) expandChildren(ui->treeView, model, QModelIndex()); + // item activated connect(ui->treeView, &QTreeView::activated, this, [this](const QModelIndex &index) { if(index.column() == 1) emit openUrl(index.data(Qt::DisplayRole).toUrl()); else { - auto *dlg = new EditBookmarkDialog(model, index, this); - if(dlg->exec() == QDialog::Accepted) { - m_isChanged = true; - } + editBookmark(index); } }); + + // addBookmark + connect(ui->addBookmark_toolButton, &QToolButton::clicked, this, [this]() { + const QModelIndex idx = model->parentFolder(ui->treeView->currentIndex()); + const QModelIndex childIdx = model->appendBookmark(tr("Title"), QString(), idx); + ui->treeView->setCurrentIndex(childIdx); + editBookmark(childIdx); + m_isChanged = true; + }); + + // addFolder + connect(ui->addFolder_toolButton, &QToolButton::clicked, this, [this]() { + const QModelIndex idx = model->parentFolder(ui->treeView->currentIndex()); + const QModelIndex childIdx = model->appendFolder(tr("Title"), idx); + ui->treeView->setCurrentIndex(childIdx); + editBookmark(childIdx); + m_isChanged = true; + }); + + // deleteItem + connect(ui->deleteItem_toolButton, &QToolButton::clicked, this, [this]() { + const QModelIndex idx = ui->treeView->currentIndex(); + if(model->removeRow(idx.row(), idx.parent())) + m_isChanged = true; + }); } BookmarksWidget::~BookmarksWidget() @@ -73,6 +96,14 @@ BookmarksWidget::~BookmarksWidget() delete ui; } + +void BookmarksWidget::editBookmark(const QModelIndex &index) +{ + auto *dlg = new EditBookmarkDialog(model, index, this); + if(dlg->exec() == QDialog::Accepted) + m_isChanged = true; +} + void BookmarksWidget::save() { if(!m_isChanged) { diff --git a/lib/bookmarks/bookmarkswidget.h b/lib/bookmarks/bookmarkswidget.h index aa15033..73628df 100644 --- a/lib/bookmarks/bookmarkswidget.h +++ b/lib/bookmarks/bookmarkswidget.h @@ -29,6 +29,9 @@ public: explicit BookmarksWidget(const QString &path, QWidget *parent = nullptr); ~BookmarksWidget() override; +protected: + void editBookmark(const QModelIndex &index); + signals: void openUrl(const QUrl &url); diff --git a/lib/bookmarks/forms/editbookmarkdialog.cpp b/lib/bookmarks/forms/editbookmarkdialog.cpp index a01f4ac..9c6efa0 100644 --- a/lib/bookmarks/forms/editbookmarkdialog.cpp +++ b/lib/bookmarks/forms/editbookmarkdialog.cpp @@ -7,8 +7,8 @@ */ #include "editbookmarkdialog.h" -#include "../bookmarkitem.h" -#include "../bookmarkmodel.h" +#include "model/bookmarkitem.h" +#include "model/bookmarkmodel.h" #include "ui_editbookmarkdialog.h" EditBookmarkDialog::EditBookmarkDialog(BookmarkModel *model, const QModelIndex &index, QWidget *parent) @@ -67,4 +67,4 @@ void EditBookmarkDialog::saveChanges() if(descriptionChanged) { m_model->setData(m_index, ui->description->toPlainText(), BookmarkItem::Description, Qt::DisplayRole); } -} \ No newline at end of file +} diff --git a/lib/bookmarks/model/bookmarkitem.cpp b/lib/bookmarks/model/bookmarkitem.cpp new file mode 100644 index 0000000..4715b6f --- /dev/null +++ b/lib/bookmarks/model/bookmarkitem.cpp @@ -0,0 +1,136 @@ +/* + * 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 "bookmarkitem.h" +#include +#include + +BookmarkItem::BookmarkItem(const QVector &data, Type type, BookmarkItem *parent) +{ + m_parentItem = parent; + + m_type = type; + if(m_type == Folder) { + m_icon.addPixmap(qApp->style()->standardPixmap(QStyle::SP_DirClosedIcon), QIcon::Normal, QIcon::Off); + m_icon.addPixmap(qApp->style()->standardPixmap(QStyle::SP_DirOpenIcon), QIcon::Normal, QIcon::On); + } else if(m_type == Bookmark) + m_icon.addPixmap(qApp->style()->standardPixmap(QStyle::SP_FileIcon)); + + m_data.resize(FieldCount); + for(int i = 0; i < FieldCount; ++i) { + m_data[i] = data.value(i, QVariant()); + } +} + +BookmarkItem::~BookmarkItem() +{ + qDeleteAll(m_children); +} + +BookmarkItem *BookmarkItem::parent() const +{ + return m_parentItem; +} + +bool BookmarkItem::appendChild(BookmarkItem *childItem) +{ + // Only folders can have children, so only append them on folders + // This way, we don't need to add checks to the other methods + if(m_type == Folder || m_type == Root) { + m_children.append(childItem); + return true; + } + + return false; +} + +bool BookmarkItem::insertChild(int position, BookmarkItem *childItem) +{ + if(m_type == Folder || m_type == Root) { + m_children.insert(position, childItem); + return true; + } + + return false; +} + +bool BookmarkItem::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; +} + +BookmarkItem *BookmarkItem::takeChild(int index, BookmarkItem *newParent) +{ + m_children[index]->m_parentItem = newParent; + return m_children.takeAt(index); +} + +BookmarkItem *BookmarkItem::child(int index) const +{ + return m_children.value(index); +} + +int BookmarkItem::childCount() const +{ + return m_children.count(); +} + +QVariant BookmarkItem::data(Fields column) const +{ + return m_data.value(column); +} + +bool BookmarkItem::setData(Fields column, const QVariant &data) +{ + if(column >= FieldCount) + return false; + + m_data[column] = data; + return true; +} + +QIcon BookmarkItem::icon() const +{ + return m_icon; +} + +bool BookmarkItem::isExpanded() const +{ + return m_isExpanded; +} + +void BookmarkItem::setExpanded(bool expanded) +{ + if(m_type == BookmarkItem::Folder) + m_isExpanded = expanded; +} + +QString BookmarkItem::tooltip() const +{ + return m_data.value(Tags).toStringList().join(", "); +} + +BookmarkItem::Type BookmarkItem::type() const +{ + return m_type; +} + +int BookmarkItem::row() const +{ + if(m_parentItem) + return m_parentItem->m_children.indexOf(const_cast(this)); + + return 0; +} diff --git a/lib/bookmarks/model/bookmarkitem.h b/lib/bookmarks/model/bookmarkitem.h new file mode 100644 index 0000000..8c9463f --- /dev/null +++ b/lib/bookmarks/model/bookmarkitem.h @@ -0,0 +1,69 @@ +/* + * 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 + */ + +#ifndef SMOLBOTE_BOOKMARKITEM_H +#define SMOLBOTE_BOOKMARKITEM_H + +#include +#include +#include + +class BookmarkItem +{ +public: + enum Type { + Root, + Folder, + Bookmark, + }; + + enum Fields { + Title, + Href, + Tags, + Description, + FieldCount + }; + + explicit BookmarkItem(const QVector &data, Type type, BookmarkItem *parent = nullptr); + ~BookmarkItem(); + + BookmarkItem *parent() const; + + bool appendChild(BookmarkItem *childItem); + bool insertChild(int position, BookmarkItem *childItem); + bool removeChildAt(int index, int count = 1); + BookmarkItem *takeChild(int index, BookmarkItem *newParent); + + BookmarkItem *child(int index) const; + int childCount() const; + + QVariant data(Fields column) const; + bool setData(Fields column, const QVariant &data); + + QIcon icon() const; + bool isExpanded() const; + void setExpanded(bool expanded); + + QString tooltip() const; + Type type() const; + int row() const; + +private: + QVector m_children; + BookmarkItem *m_parentItem; + + Type m_type; + QIcon m_icon; + bool m_isExpanded = false; + + // fields + QVector m_data; +}; + +#endif // SMOLBOTE_BOOKMARKITEM_H diff --git a/lib/bookmarks/model/bookmarkmodel.cpp b/lib/bookmarks/model/bookmarkmodel.cpp new file mode 100644 index 0000000..aa8b0ac --- /dev/null +++ b/lib/bookmarks/model/bookmarkmodel.cpp @@ -0,0 +1,336 @@ +/* + * 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 "xbel.h" +#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 }); + 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 }); + 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(index.internalPointer())->isExpanded(); +} + +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(); + + 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(); + + 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(); + 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; + } + return true; +} diff --git a/lib/bookmarks/model/bookmarkmodel.h b/lib/bookmarks/model/bookmarkmodel.h new file mode 100644 index 0000000..4da2f9c --- /dev/null +++ b/lib/bookmarks/model/bookmarkmodel.h @@ -0,0 +1,61 @@ +/* + * 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 + */ + +#ifndef SMOLBOTE_BOOKMARKMODEL_H +#define SMOLBOTE_BOOKMARKMODEL_H + +#include "bookmarkitem.h" +#include + +class BookmarkModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit BookmarkModel(QObject *parent = nullptr); + ~BookmarkModel() override; + + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant data(const QModelIndex &index, int column, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + bool setData(const QModelIndex &index, const QVariant &value, BookmarkItem::Fields column, int role); + Qt::ItemFlags flags(const QModelIndex &index) const override; + + bool isItemExpanded(const QModelIndex &index) const; + + int rowCount(const QModelIndex &index) const override; + QModelIndex appendBookmark(const QString &title, const QString &url, const QModelIndex &parent); + QModelIndex appendFolder(const QString &title, const QModelIndex &parent); + bool removeRows(int position, int rows, const QModelIndex &parent) override; + int columnCount(const QModelIndex &index) const override; + + Qt::DropActions supportedDropActions() const override; + QStringList mimeTypes() const override; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + bool dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &index) const override; + QModelIndex parentFolder(const QModelIndex &index) const; + + BookmarkItem *root() + { + return rootItem; + } + + QStringList search(const QString &term) const; + +private: + const QLatin1Literal mimeType = QLatin1Literal("application/xbel"); + + BookmarkItem *getItem(const QModelIndex &index) const; + BookmarkItem *rootItem; +}; + +#endif // SMOLBOTE_BOOKMARKMODEL_H diff --git a/lib/bookmarks/xbel.cpp b/lib/bookmarks/xbel.cpp index 174995d..1cb5756 100644 --- a/lib/bookmarks/xbel.cpp +++ b/lib/bookmarks/xbel.cpp @@ -7,7 +7,7 @@ */ #include "xbel.h" -#include "bookmarkitem.h" +#include "model/bookmarkitem.h" #include #include -- cgit v1.2.1