aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFelix (xq) Queißner <git@mq32.de>2020-06-29 20:17:42 +0200
committerFelix (xq) Queißner <git@mq32.de>2020-06-29 20:17:42 +0200
commit8e910f26a28b1b1beae363e6c19f39224f74e2e8 (patch)
tree282b768b54c0966578944a5b2cd717ba0fe58cfa /src
parentdcba6f90718d3f009380ad2b2994790866b881e2 (diff)
downloadkristall-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.cpp19
-rw-r--r--src/favouritecollection.cpp649
-rw-r--r--src/favouritecollection.cpp.bak144
-rw-r--r--src/favouritecollection.hpp129
-rw-r--r--src/favouritecollection.hpp.bak48
-rw-r--r--src/main.cpp58
-rw-r--r--src/mainwindow.cpp6
-rw-r--r--src/mainwindow.ui12
-rw-r--r--src/protocols/abouthandler.cpp4
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");