diff options
| author | Felix (xq) Queißner <git@mq32.de> | 2020-06-29 20:17:42 +0200 |
|---|---|---|
| committer | Felix (xq) Queißner <git@mq32.de> | 2020-06-29 20:17:42 +0200 |
| commit | 8e910f26a28b1b1beae363e6c19f39224f74e2e8 (patch) | |
| tree | 282b768b54c0966578944a5b2cd717ba0fe58cfa /src | |
| parent | dcba6f90718d3f009380ad2b2994790866b881e2 (diff) | |
| download | kristall-8e910f26a28b1b1beae363e6c19f39224f74e2e8.tar.gz | |
Paves road for new favourite system: Refactors the favourites into tree structure instead of flat list.
Diffstat (limited to 'src')
| -rw-r--r-- | src/browsertab.cpp | 19 | ||||
| -rw-r--r-- | src/favouritecollection.cpp | 649 | ||||
| -rw-r--r-- | src/favouritecollection.cpp.bak | 144 | ||||
| -rw-r--r-- | src/favouritecollection.hpp | 129 | ||||
| -rw-r--r-- | src/favouritecollection.hpp.bak | 48 | ||||
| -rw-r--r-- | src/main.cpp | 58 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 6 | ||||
| -rw-r--r-- | src/mainwindow.ui | 12 | ||||
| -rw-r--r-- | src/protocols/abouthandler.cpp | 4 |
9 files changed, 958 insertions, 111 deletions
diff --git a/src/browsertab.cpp b/src/browsertab.cpp index 47163d3..fcd809e 100644 --- a/src/browsertab.cpp +++ b/src/browsertab.cpp @@ -176,15 +176,24 @@ void BrowserTab::toggleIsFavourite() toggleIsFavourite(not this->ui->fav_button->isChecked()); } -void BrowserTab::toggleIsFavourite(bool isFavourite) +void BrowserTab::toggleIsFavourite(bool shouldBeFavourite) { - if (isFavourite) + // isFavourite is the "new" state of the checkbox, so when it's true + // we yet need to add it. + if (shouldBeFavourite) { - kristall::favourites.add(this->current_location); + kristall::favourites.addUnsorted(this->current_location); } else { - kristall::favourites.remove(this->current_location); + auto answer = QMessageBox::question( + this, + "Kristall", + tr("Do you really want to remove this page from your favourites?") + ); + if(answer != QMessageBox::Yes) + return; + kristall::favourites.removeUrl(this->current_location); } this->updateUI(); @@ -934,7 +943,7 @@ void BrowserTab::updateUI() this->ui->stop_button->setVisible(in_progress); this->ui->fav_button->setEnabled(this->successfully_loaded); - this->ui->fav_button->setChecked(kristall::favourites.contains(this->current_location)); + this->ui->fav_button->setChecked(kristall::favourites.containsUrl(this->current_location)); } bool BrowserTab::trySetClientCertificate(const QString &query) 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; +} diff --git a/src/favouritecollection.cpp.bak b/src/favouritecollection.cpp.bak new file mode 100644 index 0000000..7b9bec5 --- /dev/null +++ b/src/favouritecollection.cpp.bak @@ -0,0 +1,144 @@ +#include "favouritecollection.hpp" + +#include <QFile> + +FavouriteCollection::FavouriteCollection(QObject *parent) : + QAbstractListModel(parent) +{ + +} + +void FavouriteCollection::add(QUrl const & url) +{ + if(contains(url)) + return; + + beginInsertRows(QModelIndex{}, items.size(), items.size() + 1); + items.push_back(url); + endInsertRows(); +} + +void FavouriteCollection::remove(QUrl const & url) +{ + for(int i = 0; i < items.size(); i++) + { + if(items.at(i) == url) { + beginRemoveRows(QModelIndex{}, i, i + 1); + items.removeAt(i); + endRemoveRows(); + return; + } + } +} + +bool FavouriteCollection::contains(const QUrl &url) +{ + for(auto const & item : items) { + if(item == url) + return true; + } + return false; +} + +QUrl FavouriteCollection::get(const QModelIndex &index) const +{ + if(index.isValid()) { + return items.at(index.row()); + } else { + return QUrl { }; + } +} + +bool FavouriteCollection::save(const QString &fileName) const +{ + QFile file(fileName); + if(not file.open(QFile::WriteOnly)) + return false; + + for(auto const & url: items) + { + QByteArray blob = (url.toString() + "\n").toUtf8(); + + qint64 offset = 0; + while(offset < blob.size()) + { + auto len = file.write(blob.data() + offset, blob.size() - offset); + if(len <= 0) { + file.close(); + return false; + } + offset += len; + } + } + + file.close(); + return true; +} + +bool FavouriteCollection::save(QSettings &settings) const +{ + settings.beginWriteArray("favourites", items.size()); + for(int i = 0; i < items.size(); i++) + { + settings.setArrayIndex(i); + settings.setValue("url", items[i].toString()); + } + settings.endArray(); + return true; +} + +bool FavouriteCollection::load(const QString &fileName) +{ + QFile file(fileName); + if(not file.open(QFile::ReadOnly)) + return false; + auto data = file.readAll(); + + beginResetModel(); + + items.clear(); + for(auto line : data.split('\n')) { + if(line.size() > 0) { + items.push_back(QUrl(QString::fromUtf8(line))); + } + } + endResetModel(); + + return true; +} + +bool FavouriteCollection::load(QSettings & settings) +{ + int len = settings.beginReadArray("favourites"); + items.resize(len); + for(int i = 0; i < items.size(); i++) + { + settings.setArrayIndex(i); + items[i] = settings.value("url").toString(); + } + settings.endArray(); + return true; +} + +int FavouriteCollection::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return items.size(); +} + +bool FavouriteCollection::setData(const QModelIndex &index, const QVariant &value, int role) +{ + Q_UNUSED(value) + Q_UNUSED(index) + Q_UNUSED(role) + return false; +} + +QVariant FavouriteCollection::data(const QModelIndex &index, int role) const +{ + if(role != Qt::DisplayRole) { + return QVariant{}; + } + return items.at(index.row()).toString(); +} + diff --git a/src/favouritecollection.hpp b/src/favouritecollection.hpp index 043fdf2..ea107ab 100644 --- a/src/favouritecollection.hpp +++ b/src/favouritecollection.hpp @@ -1,48 +1,141 @@ #ifndef FAVOURITECOLLECTION_HPP #define FAVOURITECOLLECTION_HPP -#include <QObject> -#include <QAbstractListModel> +#include <QAbstractItemModel> #include <QUrl> +#include <QString> +#include <memory> #include <QSettings> +struct Favourite +{ + QString title; + QUrl destination; + + bool isValid() const { + return destination.isValid(); + } -class FavouriteCollection : public QAbstractListModel + QString getTitle() const { + if(title.isEmpty()) + return destination.toString(QUrl::FullyDecoded); + else + return title; + } +}; + +class FavouriteCollection : public QAbstractItemModel { Q_OBJECT + struct Node { + enum Type { Root, Group, Favourite }; + Node * parent = nullptr; + int index = 0; + std::vector<std::unique_ptr<Node>> children; + Type type; + explicit Node(Type t) : type(t) { } + virtual ~Node() = default; + + template<typename T> + T & as() { return *static_cast<T*>(this); } + + template<typename T> + T const & as() const { return *static_cast<T const*>(this); } + }; + + struct FavouriteNode : Node { + ::Favourite favourite; + FavouriteNode() : Node(Favourite) { } + ~FavouriteNode() override = default; + }; + + struct GroupNode : Node { + QString title; + GroupNode() : Node(Group) { } + ~GroupNode() override = default; + }; + + struct RootNode : Node { + RootNode() : Node(Root) { } + ~RootNode() override = default; + }; + public: explicit FavouriteCollection(QObject *parent = nullptr); - void add(QUrl const & url); + FavouriteCollection(FavouriteCollection const & other); - void remove(QUrl const & url); + FavouriteCollection & operator=(FavouriteCollection const &); + FavouriteCollection & operator=(FavouriteCollection &&); - bool contains(QUrl const & url); +public: + void load(QSettings & settings); - QUrl get(QModelIndex const & index) const ; + void save(QSettings & settings) const; - bool save(QString const & fileName) const; - bool save(QSettings & settings) const; + bool addGroup(QString const & group); - bool load(QString const & fileName); - bool load(QSettings & settings); + bool addFavourite(QString const & group, Favourite const & fav); - QVector<QUrl> getAll() const { - return this->items; - } + Favourite getFavourite(QModelIndex const & index) const; + + Favourite * getMutableFavourite(QModelIndex const & index); + + QStringList groups() const; + + //! Returns the group name of the index. + QString group(QModelIndex const & index) const; + + bool destroyFavourite(QModelIndex const & index); + + bool canDeleteGroup(QString const & group_name); + bool deleteGroup(QString const & group_name); + + //! Returns a list of non-mutable references to all contained identities + QVector<Favourite const *> allFavourites() const; + + bool containsUrl(QUrl const & url) const; + + bool addUnsorted(QUrl const & url); + + bool removeUrl(QUrl const & url); public: - int rowCount(const QModelIndex &parent = QModelIndex()) const override; + // Header: + // QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; -signals: + // Drag'n'Drop + + Qt::ItemFlags flags(const QModelIndex &index) const override; + + QStringList mimeTypes() const override; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + Qt::DropActions supportedDropActions() const override; + + Qt::DropActions supportedDragActions() const override; + + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + private: - QVector<QUrl> items; + void relayout(); + + bool internalAddGroup(QString const & group_name, GroupNode * & out_group); +private: + RootNode root; }; #endif // FAVOURITECOLLECTION_HPP diff --git a/src/favouritecollection.hpp.bak b/src/favouritecollection.hpp.bak new file mode 100644 index 0000000..043fdf2 --- /dev/null +++ b/src/favouritecollection.hpp.bak @@ -0,0 +1,48 @@ +#ifndef FAVOURITECOLLECTION_HPP +#define FAVOURITECOLLECTION_HPP + +#include <QObject> +#include <QAbstractListModel> +#include <QUrl> +#include <QSettings> + + +class FavouriteCollection : public QAbstractListModel +{ + Q_OBJECT +public: + explicit FavouriteCollection(QObject *parent = nullptr); + + void add(QUrl const & url); + + void remove(QUrl const & url); + + bool contains(QUrl const & url); + + QUrl get(QModelIndex const & index) const ; + + bool save(QString const & fileName) const; + bool save(QSettings & settings) const; + + bool load(QString const & fileName); + bool load(QSettings & settings); + + QVector<QUrl> getAll() const { + return this->items; + } + +public: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +signals: + +private: + QVector<QUrl> items; + +}; + +#endif // FAVOURITECOLLECTION_HPP diff --git a/src/main.cpp b/src/main.cpp index ef204a0..09a6a7c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) QSettings app_settings { kristall::dirs::config_root.absoluteFilePath("config.ini"), - QSettings::IniFormat + QSettings::IniFormat }; app_settings_ptr = &app_settings; @@ -120,8 +120,8 @@ int main(int argc, char *argv[]) } while(kristall::dirs::styles.exists(fileName)); QSettings style_sheet { - kristall::dirs::styles.absoluteFilePath(fileName), - QSettings::IniFormat + kristall::dirs::styles.absoluteFilePath(fileName), + QSettings::IniFormat }; style_sheet.setValue("name", name); style.save(style_sheet); @@ -147,7 +147,7 @@ int main(int argc, char *argv[]) app_settings.endGroup(); } - deprecated_settings.setValue("deprecated", true); + // deprecated_settings.setValue("deprecated", true); } else { @@ -156,6 +156,54 @@ int main(int argc, char *argv[]) } } + // Migrate to new favourites format + if(int len = app_settings.beginReadArray("favourites"); len > 0) + { + std::vector<Favourite> favs; + + favs.reserve(len); + for(int i = 0; i < len; i++) + { + app_settings.setArrayIndex(i); + + Favourite fav; + fav.destination = app_settings.value("url").toString(); + fav.title = QString { }; + + favs.emplace_back(std::move(fav)); + } + app_settings.endArray(); + + + app_settings.beginGroup("Favourites"); + { + app_settings.beginWriteArray("groups"); + + app_settings.setArrayIndex(0); + app_settings.setValue("name", QObject::tr("Unsorted")); + + { + app_settings.beginWriteArray("favourites", len); + for(int i = 0; i < len; i++) + { + auto const & fav = favs.at(i); + app_settings.setArrayIndex(i); + app_settings.setValue("title", fav.title); + app_settings.setValue("url", fav.destination); + } + app_settings.endArray(); + } + + app_settings.endArray(); + } + app_settings.endGroup(); + + app_settings.remove("favourites"); + } + else { + app_settings.endArray(); + } + kristall::settings = &app_settings; kristall::options.load(app_settings); @@ -180,7 +228,9 @@ int main(int argc, char *argv[]) kristall::document_style.load(app_settings); app_settings.endGroup(); + app_settings.beginGroup("Favourites"); kristall::favourites.load(app_settings); + app_settings.endGroup(); kristall::setTheme(kristall::options.theme); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 79352cb..b987e7d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -52,7 +52,7 @@ MainWindow::MainWindow(QApplication * app, QWidget *parent) : connect(this->ui->menuNavigation, &QMenu::aboutToShow, [this]() { BrowserTab * tab = qobject_cast<BrowserTab*>(this->ui->browser_tabs->currentWidget()); if(tab != nullptr) { - ui->actionAdd_to_favourites->setChecked(kristall::favourites.contains(tab->current_location)); + ui->actionAdd_to_favourites->setChecked(kristall::favourites.containsUrl(tab->current_location)); } }); @@ -147,7 +147,7 @@ void MainWindow::on_browser_tabs_currentChanged(int index) void MainWindow::on_favourites_view_doubleClicked(const QModelIndex &index) { - if(auto url = kristall::favourites.get(index); url.isValid()) { + if(auto url = kristall::favourites.getFavourite(index).destination; url.isValid()) { this->addNewTab(true, url); } } @@ -390,7 +390,7 @@ void MainWindow::on_history_view_customContextMenuRequested(const QPoint &pos) void MainWindow::on_favourites_view_customContextMenuRequested(const QPoint &pos) { if(auto idx = this->ui->favourites_view->indexAt(pos); idx.isValid()) { - if(QUrl url = kristall::favourites.get(idx); url.isValid()) { + if(QUrl url = kristall::favourites.getFavourite(idx).destination; url.isValid()) { QMenu menu; BrowserTab * tab = qobject_cast<BrowserTab*>(this->ui->browser_tabs->currentWidget()); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 886bd89..2904969 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -120,7 +120,17 @@ <number>0</number> </property> <item> - <widget class="QListView" name="favourites_view"/> + <widget class="QTreeView" name="favourites_view"> + <property name="dragDropMode"> + <enum>QAbstractItemView::InternalMove</enum> + </property> + <property name="defaultDropAction"> + <enum>Qt::MoveAction</enum> + </property> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + </widget> </item> </layout> </widget> diff --git a/src/protocols/abouthandler.cpp b/src/protocols/abouthandler.cpp index f87afb8..53646d9 100644 --- a/src/protocols/abouthandler.cpp +++ b/src/protocols/abouthandler.cpp @@ -28,9 +28,9 @@ bool AboutHandler::startRequest(const QUrl &url, ProtocolHandler::RequestOptions document.append("# Favourites\n"); document.append("\n"); - for (auto const &fav : kristall::favourites.getAll()) + for (auto const &fav : kristall::favourites.allFavourites()) { - document.append("=> " + fav.toString().toUtf8() + "\n"); + document.append("=> " + fav->destination.toString().toUtf8() + "\n"); } this->requestComplete(document, "text/gemini"); |
