diff --git a/framework/qml/FolderListView.qml b/framework/qml/FolderListView.qml index b00c8630..6079863e 100644 --- a/framework/qml/FolderListView.qml +++ b/framework/qml/FolderListView.qml @@ -1,42 +1,45 @@ /* Copyright (C) 2016 Michael Bohlender, Copyright (C) 2017 Christian Mollekopf, This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.4 import org.kube.framework 1.0 as Kube Kube.TreeView { id: root property variant accountId Kube.Listener { filter: Kube.Messages.selectNextFolder onMessageReceived: root.selectNext() } Kube.Listener { filter: Kube.Messages.selectPreviousFolder onMessageReceived: root.selectPrevious() } model: Kube.FolderListModel { id: folderListModel accountId: root.accountId + onInitialItemsLoaded: { + root.selectRootIndex() + } } } diff --git a/framework/qml/TreeView.qml b/framework/qml/TreeView.qml index 0b06c590..939f1401 100644 --- a/framework/qml/TreeView.qml +++ b/framework/qml/TreeView.qml @@ -1,109 +1,113 @@ /* Copyright (C) 2016 Michael Bohlender, Copyright (C) 2017 Christian Mollekopf, This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2 import QtQuick.Controls 2 import QtQuick.Layouts 1 import org.kube.framework 1.0 as Kube FocusScope { id: root property var model: null readonly property var currentIndex: modelAdaptor.mapRowToModelIndex(listView.currentIndex) + function selectRootIndex() { + listView.currentIndex = 0 + } + function selectNext() { listView.incrementCurrentIndex() } function selectPrevious() { listView.decrementCurrentIndex() } Kube.ListView { id: listView anchors.fill: parent focus: true model: Kube.TreeModelAdaptor { id: modelAdaptor model: root.model } ScrollBar.vertical: Kube.ScrollBar { invertedColors: true } delegate: Kube.ListDelegate { id: delegate width: listView.availableWidth height: Kube.Units.gridUnit * 1.5 hoverEnabled: true property bool isActive: listView.currentIndex === index background: Kube.DelegateBackground { anchors.fill: parent color: Kube.Colors.textColor focused: delegate.activeFocus || delegate.hovered selected: isActive } function toggleExpanded() { var idx = model._q_TreeView_ModelIndex if (modelAdaptor.isExpanded(idx)) { modelAdaptor.collapse(idx) } else { modelAdaptor.expand(idx) } } Keys.onSpacePressed: toggleExpanded() RowLayout { anchors { fill: parent leftMargin: 2 + (model._q_TreeView_ItemDepth + 1) * Kube.Units.largeSpacing } spacing: Kube.Units.smallSpacing Kube.Label { id: label Layout.fillWidth: true text: model.name color: Kube.Colors.highlightedTextColor elide: Text.ElideLeft clip: false Kube.IconButton { anchors { right: label.left verticalCenter: label.verticalCenter } visible: model._q_TreeView_HasChildren iconName: model._q_TreeView_ItemExpanded ? Kube.Icons.goDown_inverted : Kube.Icons.goNext_inverted padding: 0 width: Kube.Units.gridUnit height: Kube.Units.gridUnit onClicked: toggleExpanded() activeFocusOnTab: false hoverEnabled: false } } } } } } diff --git a/framework/src/domain/folderlistmodel.cpp b/framework/src/domain/folderlistmodel.cpp index cdc1461a..208819af 100644 --- a/framework/src/domain/folderlistmodel.cpp +++ b/framework/src/domain/folderlistmodel.cpp @@ -1,232 +1,237 @@ /* Copyright (c) 2016 Michael Bohlender Copyright (c) 2016 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "folderlistmodel.h" #include #include #include #include #include using namespace Sink; using namespace Sink::ApplicationDomain; FolderListModel::FolderListModel(QObject *parent) : KRecursiveFilterProxyModel(parent) { setDynamicSortFilter(true); sort(0, Qt::AscendingOrder); //Automatically fetch all folders, otherwise the recursive filtering does not work. QObject::connect(this, &QSortFilterProxyModel::sourceModelChanged, [this] () { if (sourceModel()) { QObject::connect(sourceModel(), &QAbstractItemModel::rowsInserted, sourceModel(), [this] (QModelIndex parent, int first, int last) { for (int row = first; row <= last; row++) { auto idx = sourceModel()->index(row, 0, parent); sourceModel()->fetchMore(idx); } }); + QObject::connect(sourceModel(), &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, const QVector &roles) { + if (roles.contains(Sink::Store::ChildrenFetchedRole)) { + emit initialItemsLoaded(); + } + }); } }); } FolderListModel::~FolderListModel() { } QHash< int, QByteArray > FolderListModel::roleNames() const { QHash roles; roles[Name] = "name"; roles[Icon] = "icon"; roles[Id] = "id"; roles[DomainObject] = "domainObject"; roles[Status] = "status"; roles[Trash] = "trash"; roles[HasNewData] = "hasNewData"; return roles; } QVariant FolderListModel::data(const QModelIndex &idx, int role) const { auto srcIdx = mapToSource(idx); auto folder = srcIdx.data(Sink::Store::DomainObjectRole).value(); switch (role) { case Name: return folder->getName(); case Icon: return folder->getIcon(); case Id: return folder->identifier(); case DomainObject: return QVariant::fromValue(folder); case Status: { switch (srcIdx.data(Sink::Store::StatusRole).toInt()) { case Sink::ApplicationDomain::SyncStatus::SyncInProgress: return InProgressStatus; case Sink::ApplicationDomain::SyncStatus::SyncError: return ErrorStatus; case Sink::ApplicationDomain::SyncStatus::SyncSuccess: return SuccessStatus; } return NoStatus; } case Trash: if (folder) { return folder->getSpecialPurpose().contains(Sink::ApplicationDomain::SpecialPurpose::Mail::trash); } return false; case HasNewData: return mHasNewData.contains(folder->identifier()); } return QSortFilterProxyModel::data(idx, role); } static QModelIndex findRecursive(QAbstractItemModel *model, const QModelIndex &parent, int role, const QVariant &value) { for (auto row = 0; row < model->rowCount(parent); row++) { const auto idx = model->index(row, 0, parent); if (model->data(idx, role) == value) { return idx; } auto result = findRecursive(model, idx, role, value); if (result.isValid()) { return result; } } return {}; } void FolderListModel::runQuery(const Query &query) { mModel = Store::loadModel(query); setSourceModel(mModel.data()); Sink::Query resourceQuery; resourceQuery.setFilter(query.getResourceFilter()); mNotifier.reset(new Sink::Notifier{resourceQuery}); mNotifier->registerHandler([&](const Sink::Notification ¬ification) { if (notification.type == Sink::Notification::Info && notification.code == ApplicationDomain::NewContentAvailable) { if (!notification.entities.isEmpty()) { mHasNewData.insert(notification.entities.first()); auto idx = findRecursive(this, {}, Id, QVariant::fromValue(notification.entities.first())); if (idx.isValid()) { emit dataChanged(idx, idx); } } } }); } void FolderListModel::setAccountId(const QVariant &accountId) { const auto account = accountId.toString().toUtf8(); //Get all folders of an account auto query = Query(); query.resourceFilter(account); query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus); query.request() .request() .request() .request() .request(); query.requestTree(); query.setId("foldertree" + account); runQuery(query); } QVariant FolderListModel::accountId() const { return {}; } static int getPriority(const Sink::ApplicationDomain::Folder &folder) { auto specialPurpose = folder.getSpecialPurpose(); if (specialPurpose.contains(Sink::ApplicationDomain::SpecialPurpose::Mail::inbox)) { return 5; } else if (specialPurpose.contains(Sink::ApplicationDomain::SpecialPurpose::Mail::drafts)) { return 6; } else if (specialPurpose.contains(Sink::ApplicationDomain::SpecialPurpose::Mail::sent)) { return 7; } else if (specialPurpose.contains(Sink::ApplicationDomain::SpecialPurpose::Mail::trash)) { return 8; } else if (!specialPurpose.isEmpty()) { return 9; } return 10; } bool FolderListModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { const auto leftFolder = left.data(Sink::Store::DomainObjectRole).value(); const auto rightFolder = right.data(Sink::Store::DomainObjectRole).value(); const auto leftPriority = getPriority(*leftFolder); const auto rightPriority = getPriority(*rightFolder); if (leftPriority == rightPriority) { return leftFolder->getName() < rightFolder->getName(); } return leftPriority < rightPriority; } bool FolderListModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const { auto index = sourceModel()->index(sourceRow, 0, sourceParent); Q_ASSERT(index.isValid()); const auto folder = index.data(Sink::Store::DomainObjectRole).value(); Q_ASSERT(folder); const auto enabled = folder->getEnabled(); return enabled; } void FolderListModel::fetchMore(const QModelIndex &parent) { mHasNewData.remove(parent.data(Id).toByteArray()); QAbstractItemModel::fetchMore(parent); } void FolderListModel::setFolderId(const QVariant &folderId) { const auto folder = folderId.toString().toUtf8(); if (folder.isEmpty()) { setSourceModel(nullptr); mModel.clear(); return; } //Get all folders of an account auto query = Query(); query.filter(folder); query.request() .request() .request() .request() .request(); query.setId("folder" + folder); runQuery(query); } QVariant FolderListModel::folderId() const { return {}; } diff --git a/framework/src/domain/folderlistmodel.h b/framework/src/domain/folderlistmodel.h index d367ea5f..42f977cf 100644 --- a/framework/src/domain/folderlistmodel.h +++ b/framework/src/domain/folderlistmodel.h @@ -1,81 +1,85 @@ /* Copyright (c) 2016 Michael Bohlender Copyright (c) 2016 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once #include "kube_export.h" #include #include #include #include namespace Sink { class Query; } class KUBE_EXPORT FolderListModel : public KRecursiveFilterProxyModel { Q_OBJECT Q_PROPERTY (QVariant accountId READ accountId WRITE setAccountId) Q_PROPERTY (QVariant folderId READ folderId WRITE setFolderId) public: enum Status { NoStatus, InProgressStatus, ErrorStatus, SuccessStatus, }; Q_ENUMS(Status) FolderListModel(QObject *parent = Q_NULLPTR); ~FolderListModel(); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; enum Roles { Name = Qt::UserRole + 1, Icon, Id, DomainObject, Status, Trash, HasNewData }; Q_ENUMS(Roles) QHash roleNames() const Q_DECL_OVERRIDE; void setAccountId(const QVariant &accountId); QVariant accountId() const; void setFolderId(const QVariant &folderId); QVariant folderId() const; + +signals: + void initialItemsLoaded(); + protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE; void fetchMore(const QModelIndex &left) Q_DECL_OVERRIDE; private: void runQuery(const Sink::Query &query); QSharedPointer mModel; QSet mHasNewData; QScopedPointer mNotifier; };