aboutsummaryrefslogtreecommitdiff
path: root/src/favouritecollection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/favouritecollection.cpp')
-rw-r--r--src/favouritecollection.cpp649
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;
+}