diff options
Diffstat (limited to 'src/favouritecollection.cpp')
| -rw-r--r-- | src/favouritecollection.cpp | 649 |
1 files changed, 571 insertions, 78 deletions
diff --git a/src/favouritecollection.cpp b/src/favouritecollection.cpp index 7b9bec5..06329fe 100644 --- a/src/favouritecollection.cpp +++ b/src/favouritecollection.cpp @@ -1,144 +1,637 @@ #include "favouritecollection.hpp" -#include <QFile> +#include <cassert> +#include <QDebug> +#include <QIcon> +#include <QMimeData> -FavouriteCollection::FavouriteCollection(QObject *parent) : - QAbstractListModel(parent) +#include <memory> + +FavouriteCollection::FavouriteCollection(QObject *parent) + : QAbstractItemModel(parent) { } -void FavouriteCollection::add(QUrl const & url) +FavouriteCollection::FavouriteCollection(const FavouriteCollection &other) { - if(contains(url)) - return; + for(auto const & grp : other.root.children) + { + auto const & src_group = grp->as<GroupNode>(); + auto dst_group = std::make_unique<GroupNode>(); - beginInsertRows(QModelIndex{}, items.size(), items.size() + 1); - items.push_back(url); - endInsertRows(); + dst_group->title = src_group.title; + + for(auto const & id : src_group.children) { + auto const & src_id = id->as<FavouriteNode>(); + auto dst_id = std::make_unique<FavouriteNode>(); + + dst_id->favourite = src_id.favourite; + + dst_group->children.emplace_back(std::move(dst_id)); + } + + root.children.emplace_back(std::move(dst_group)); + } + + + relayout(); } -void FavouriteCollection::remove(QUrl const & url) +FavouriteCollection &FavouriteCollection::operator=(const FavouriteCollection & other) { - for(int i = 0; i < items.size(); i++) + beginResetModel(); + + root.children.clear(); + for(auto const & grp : other.root.children) { - if(items.at(i) == url) { - beginRemoveRows(QModelIndex{}, i, i + 1); - items.removeAt(i); - endRemoveRows(); - return; + auto const & src_group = grp->as<GroupNode>(); + auto dst_group = std::make_unique<GroupNode>(); + + dst_group->title = src_group.title; + + for(auto const & id : src_group.children) { + auto const & src_id = id->as<FavouriteNode>(); + auto dst_id = std::make_unique<FavouriteNode>(); + + dst_id->favourite = src_id.favourite; + + dst_group->children.emplace_back(std::move(dst_id)); } + + root.children.emplace_back(std::move(dst_group)); } + + this->relayout(); + endResetModel(); + return *this; } -bool FavouriteCollection::contains(const QUrl &url) +FavouriteCollection &FavouriteCollection::operator=(FavouriteCollection && other) { - for(auto const & item : items) { - if(item == url) - return true; - } - return false; + beginResetModel(); + this->root.children = std::move(other.root.children); + this->relayout(); + endResetModel(); + return *this; } -QUrl FavouriteCollection::get(const QModelIndex &index) const +void FavouriteCollection::load(QSettings &settings) { - if(index.isValid()) { - return items.at(index.row()); - } else { - return QUrl { }; + this->beginResetModel(); + + this->root.children.clear(); + + int group_cnt = settings.beginReadArray("groups"); + for(int i = 0; i < group_cnt; i++) + { + settings.setArrayIndex(i); + auto group = std::make_unique<GroupNode>(); + + group->title = settings.value("name").toString(); + + int id_cnt = settings.beginReadArray("favourites"); + + for(int j = 0; j < id_cnt; j++) + { + settings.setArrayIndex(j); + auto fav = std::make_unique<FavouriteNode>(); + + fav->favourite.title = settings.value("title").toString(); + fav->favourite.destination = settings.value("url").toUrl(); + + group->children.emplace_back(std::move(fav)); + } + + settings.endArray(); + + this->root.children.emplace_back(std::move(group)); } + settings.endArray(); + + relayout(); + + this->endResetModel(); } -bool FavouriteCollection::save(const QString &fileName) const +void FavouriteCollection::save(QSettings &settings) const { - QFile file(fileName); - if(not file.open(QFile::WriteOnly)) - return false; + settings.beginWriteArray("groups", int(root.children.size())); - for(auto const & url: items) + int grp_index = 0; + for(auto const & grp : root.children) { - QByteArray blob = (url.toString() + "\n").toUtf8(); + settings.setArrayIndex(grp_index); + grp_index += 1; + + auto & group = grp->as<GroupNode>(); + settings.setValue("name", group.title); + + settings.beginWriteArray("favourites", int(group.children.size())); - qint64 offset = 0; - while(offset < blob.size()) + int id_index = 0; + for(auto const & _id : group.children) { - auto len = file.write(blob.data() + offset, blob.size() - offset); - if(len <= 0) { - file.close(); - return false; - } - offset += len; + settings.setArrayIndex(id_index); + id_index += 1; + + auto & id = _id->as<FavouriteNode>(); + + settings.setValue("title", id.favourite.title); + settings.setValue("url", id.favourite.destination); } + + settings.endArray(); } - file.close(); + settings.endArray(); +} + +bool FavouriteCollection::addGroup(const QString &group_name) +{ + GroupNode * group; + return internalAddGroup(group_name, group); +} + +bool FavouriteCollection::addFavourite(const QString &group_name, const Favourite &fav) +{ + GroupNode * group; + internalAddGroup(group_name, group); + + QModelIndex parent_index = createIndex(group->index, 0, group); + + beginInsertRows(parent_index, group->children.size(), group->children.size() + 1); + + auto id = std::make_unique<FavouriteNode>(); + id->favourite = fav; + group->children.emplace_back(std::move(id)); + + this->relayout(); + + this->endInsertRows(); + return true; } -bool FavouriteCollection::save(QSettings &settings) const +Favourite FavouriteCollection::getFavourite(const QModelIndex &index) const +{ + if (!index.isValid()) + return Favourite(); + + if (index.column() != 0) + return Favourite(); + + Node const *item = static_cast<Node const*>(index.internalPointer()); + switch(item->type) { + case Node::Favourite: return static_cast<FavouriteNode const *>(item)->favourite; + default: + return Favourite(); + } +} + +Favourite * FavouriteCollection::getMutableFavourite(const QModelIndex &index) { - settings.beginWriteArray("favourites", items.size()); - for(int i = 0; i < items.size(); i++) + if (!index.isValid()) + return nullptr; + + if (index.column() != 0) + return nullptr; + + Node *item = static_cast<Node*>(index.internalPointer()); + switch(item->type) { + case Node::Favourite: return &static_cast<FavouriteNode *>(item)->favourite; + default: + return nullptr; + } +} + +QStringList FavouriteCollection::groups() const +{ + QStringList result; + for(auto const & grp : root.children) { - settings.setArrayIndex(i); - settings.setValue("url", items[i].toString()); + result.append(grp->as<GroupNode>().title); } - settings.endArray(); - return true; + return result; } -bool FavouriteCollection::load(const QString &fileName) +QString FavouriteCollection::group(const QModelIndex &index) const { - QFile file(fileName); - if(not file.open(QFile::ReadOnly)) + if (!index.isValid()) + return QString { }; + + Node const *item = static_cast<Node const*>(index.internalPointer()); + + switch(item->type) { + case Node::Root: return QString { }; + case Node::Group: return static_cast<GroupNode const *>(item)->title; + case Node::Favourite: return static_cast<FavouriteNode const *>(item)->parent->as<GroupNode>().title; + default: return QString { }; + } +} + +bool FavouriteCollection::destroyFavourite(const QModelIndex &index) +{ + if (!index.isValid()) return false; - auto data = file.readAll(); - beginResetModel(); + Node * childItem = static_cast<Node *>(index.internalPointer()); + Node * parent = childItem->parent; + + if (parent == &root) + return false; + + beginRemoveRows(this->parent(index), index.row(), index.row() + 1); + + parent->children.erase(parent->children.begin() + childItem->index); + + endRemoveRows(); - items.clear(); - for(auto line : data.split('\n')) { - if(line.size() > 0) { - items.push_back(QUrl(QString::fromUtf8(line))); + return true; +} + +bool FavouriteCollection::canDeleteGroup(const QString &group_name) +{ + for(auto const & group_node : root.children) + { + auto & group = group_node->as<GroupNode>(); + if((group.children.size() == 0) and (group.title == group_name)) + return true; + + } + return false; +} + +bool FavouriteCollection::deleteGroup(const QString &group_name) +{ + size_t index = 0; + for(auto it = root.children.begin(); it != root.children.end(); it++, index++) + { + auto & group = it->get()->as<GroupNode>(); + if(group.title == group_name) { + if(group.children.size() > 0) { + qDebug() << "cannot delete non-empty group" << group_name; + return false; + } + + beginRemoveRows(QModelIndex { }, index, index + 1); + + root.children.erase(it); + + endRemoveRows(); + + return true; } } - endResetModel(); + return false; +} - return true; +QVector<const Favourite *> FavouriteCollection::allFavourites() const +{ + QVector<const Favourite *> identities; + + for(auto const & group : this->root.children) + { + for(auto const & ident : group->children) + { + identities.append(&ident->as<FavouriteNode>().favourite); + } + } + + return identities; } -bool FavouriteCollection::load(QSettings & settings) +bool FavouriteCollection::containsUrl(const QUrl &url) const { - int len = settings.beginReadArray("favourites"); - items.resize(len); - for(int i = 0; i < items.size(); i++) + for(auto const & group : this->root.children) { - settings.setArrayIndex(i); - items[i] = settings.value("url").toString(); + for(auto const & ident : group->children) + { + if(ident->as<FavouriteNode>().favourite.destination == url) + return true; + } } - settings.endArray(); - return true; + return false; } -int FavouriteCollection::rowCount(const QModelIndex &parent) const +bool FavouriteCollection::addUnsorted(const QUrl &url) { - Q_UNUSED(parent) - return items.size(); + if(containsUrl(url)) + return false; + return addFavourite(tr("Unsorted"), Favourite { + QString { }, + url, + }); } -bool FavouriteCollection::setData(const QModelIndex &index, const QVariant &value, int role) +bool FavouriteCollection::removeUrl(const QUrl &url) { - Q_UNUSED(value) - Q_UNUSED(index) - Q_UNUSED(role) + for(auto const & group : this->root.children) + { + for(auto it = group->children.begin(); it != group->children.end(); it++) + { + if(it->get()->as<FavouriteNode>().favourite.destination == url) { + group->children.erase(it); + return true; + } + } + } return false; } +QModelIndex FavouriteCollection::index(int row, int column, const QModelIndex &parent) const +{ + if (not hasIndex(row, column, parent)) + return QModelIndex(); + + Node const * parentItem; + + if(!parent.isValid()) + parentItem = &this->root; + else + parentItem = static_cast<Node*>(parent.internalPointer()); + + auto & children = parentItem->children; + if(row < 0 or size_t(row) >= children.size()) + return QModelIndex { }; + return createIndex( + row, + column, + reinterpret_cast<quintptr>(children[row].get()) + ); +} + +QModelIndex FavouriteCollection::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + Node const *childItem = static_cast<Node const *>(index.internalPointer()); + Node const * parent = childItem->parent; + + if (parent == &root) + return QModelIndex(); + + return createIndex( + parent->index, + 0, + reinterpret_cast<quintptr>(parent)); +} + +int FavouriteCollection::rowCount(const QModelIndex &parent) const +{ + Node const * parentItem; + + if (!parent.isValid()) + parentItem = &root; + else + parentItem = static_cast<Node const *>(parent.internalPointer()); + + return parentItem->children.size(); +} + +int FavouriteCollection::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 1; +} + QVariant FavouriteCollection::data(const QModelIndex &index, int role) const { - if(role != Qt::DisplayRole) { - return QVariant{}; + if (!index.isValid()) + return QVariant(); + + Node const *item = static_cast<Node const*>(index.internalPointer()); + if (role == Qt::DisplayRole) + { + switch(item->type) { + case Node::Root: return "root"; + case Node::Group: return static_cast<GroupNode const *>(item)->title; + case Node::Favourite: return static_cast<FavouriteNode const *>(item)->favourite.getTitle(); + default: + return "Unknown"; + } + } + else if(role == Qt::DecorationRole) { + + switch(item->type) { + case Node::Root: return QVariant { }; + case Node::Group: return QIcon::fromTheme("folder"); + case Node::Favourite: return QIcon::fromTheme("favourite"); + default: return QVariant { }; + } + } + + return QVariant(); +} + +Qt::ItemFlags FavouriteCollection::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + Node const *item = static_cast<Node const*>(index.internalPointer()); + switch(item->type) { + case Node::Favourite: + return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled; + case Node::Group: + return QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled; + default: + return QAbstractItemModel::flags(index); + } +} + +QStringList FavouriteCollection::mimeTypes() const +{ + QStringList mimes; + mimes << "x-kristall/identity"; + return mimes; +} + +#include <QBuffer> +#include <QDataStream> + +QMimeData *FavouriteCollection::mimeData(const QModelIndexList &indexes) const +{ + if(indexes.size() != 1) + return nullptr; + auto const & index = indexes.at(0); + + if (not index.isValid()) + return nullptr; + + Node const *item = static_cast<Node const*>(index.internalPointer()); + switch(item->type) { + case Node::Favourite: { + auto const & favourite = item->as<FavouriteNode>().favourite; + + QByteArray buffer; + + { + QDataStream stream { &buffer, QIODevice::WriteOnly }; + + stream << favourite.title; + stream << favourite.destination; + } + assert(buffer.size() > 0); + + auto mime = std::make_unique<QMimeData>(); + + mime->setData("x-kristall/favourite", buffer); + + return mime.release(); + } + default: + return nullptr; + } +} + +bool FavouriteCollection::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(row); + Q_UNUSED(column); + + if (not parent.isValid()) + return false; + + Node const *item = static_cast<Node const*>(parent.internalPointer()); + switch(item->type) { + case Node::Group: { + return data->hasFormat("x-kristall/favourite") and (action == Qt::MoveAction); + } + default: + return false; + } +} + +bool FavouriteCollection::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +{ + Q_UNUSED(column); + + if(action != Qt::MoveAction) + return false; + + if (not parent.isValid()) + return false; + + Node *item = static_cast<Node *>(parent.internalPointer()); + switch(item->type) { + case Node::Group: { + auto ident_blob = data->data("x-kristall/favourite"); + + auto node = std::make_unique<FavouriteNode>(); + Favourite & fav = node->favourite; + { + QDataStream stream { &ident_blob, QIODevice::ReadOnly }; + + stream >> fav.title; + stream >> fav.destination; + } + + if(not fav.isValid()) + return false; + + auto & insert_list = item->as<GroupNode>().children; + + if((row < 0) or (size_t(row) >= insert_list.size())) { + beginInsertRows(parent, insert_list.size(), insert_list.size() + 1); + insert_list.emplace_back(std::move(node)); + } else { + beginInsertRows(parent, row, row + 1); + insert_list.emplace(insert_list.begin() + size_t(row), std::move(node)); + } + endInsertRows(); + + qDebug() << "dropping" << data->formats() << row; + + this->relayout(); + return true; } - return items.at(index.row()).toString(); + default: + return false; + } +} + +Qt::DropActions FavouriteCollection::supportedDropActions() const +{ + return Qt::MoveAction; +} + +Qt::DropActions FavouriteCollection::supportedDragActions() const +{ + return Qt::MoveAction; } +bool FavouriteCollection::removeRows(int row, int count, const QModelIndex &parent) +{ + if (not parent.isValid()) + return false; + + if(count != 1) + return false; + + Node *item = static_cast<Node *>(parent.internalPointer()); + switch(item->type) { + case Node::Group: { + auto & children = item->as<GroupNode>().children; + + if((row < 0) or (size_t(row) >= children.size())) + return false; + + beginRemoveRows(parent, row, row + 1); + children.erase(children.begin() + size_t(row)); + endRemoveRows(); + + return true; + } + default: + return false; + } +} + +void FavouriteCollection::relayout() +{ + for(size_t i = 0; i < root.children.size(); i++) + { + auto & group = *root.children[i]; + group.parent = &root; + group.index = i; + + // qDebug() << "group[" << group.index << "]" << group.as<GroupNode>().title; + + for(size_t j = 0; j < group.children.size(); j++) + { + auto & id = *group.children[j]; + id.parent = &group; + id.index = j; + assert(id.children.size() == 0); + + // qDebug() << "id[" << id.index << "]" << id.as<IdentityNode>().identity.display_name; + } + } +} + +bool FavouriteCollection::internalAddGroup(const QString &group_name, GroupNode * & group) +{ + for(auto const & grp : root.children) + { + auto * g = static_cast<GroupNode*>(grp.get()); + if(g->title == group_name) { + group = g; + return false; + } + } + + auto parent = QModelIndex { }; + + beginInsertRows(parent, this->root.children.size(), this->root.children.size() + 1); + + group = new GroupNode(); + group->title = group_name; + this->root.children.emplace_back(group); + + this->relayout(); + + endInsertRows(); + + return true; +} |
