diff --git a/src/presentation/availablenotepagesmodel.cpp b/src/presentation/availablenotepagesmodel.cpp index 8449bf99..ee196598 100644 --- a/src/presentation/availablenotepagesmodel.cpp +++ b/src/presentation/availablenotepagesmodel.cpp @@ -1,259 +1,259 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "availablenotepagesmodel.h" #include #include #include #include "presentation/availablepagessortfilterproxymodel.h" #include "presentation/noteinboxpagemodel.h" #include "presentation/querytreemodel.h" #include "presentation/tagpagemodel.h" #include "utils/jobhandler.h" using namespace Presentation; AvailableNotePagesModel::AvailableNotePagesModel(const Domain::NoteQueries::Ptr ¬eQueries, const Domain::NoteRepository::Ptr ¬eRepository, const Domain::TagQueries::Ptr &tagQueries, const Domain::TagRepository::Ptr &tagRepository, QObject *parent) : AvailablePagesModelInterface(parent), m_pageListModel(Q_NULLPTR), m_sortProxyModel(Q_NULLPTR), m_noteQueries(noteQueries), m_noteRepository(noteRepository), m_tagQueries(tagQueries), m_tagRepository(tagRepository) { } QAbstractItemModel *AvailableNotePagesModel::pageListModel() { if (!m_pageListModel) m_pageListModel = createPageListModel(); if (!m_sortProxyModel) { m_sortProxyModel = new AvailablePagesSortFilterProxyModel(this); m_sortProxyModel->setSourceModel(m_pageListModel); } return m_sortProxyModel; } bool AvailableNotePagesModel::hasProjectPages() const { return false; } bool AvailableNotePagesModel::hasContextPages() const { return false; } bool AvailableNotePagesModel::hasTagPages() const { return true; } QObject *AvailableNotePagesModel::createPageForIndex(const QModelIndex &index) { QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); if (object == m_inboxObject) { auto inboxPageModel = new NoteInboxPageModel(m_noteQueries, m_noteRepository, this); inboxPageModel->setErrorHandler(errorHandler()); return inboxPageModel; } else if (auto tag = object.objectCast()) { auto tagPageModel = new TagPageModel(tag, m_tagQueries, m_tagRepository, m_noteRepository, this); tagPageModel->setErrorHandler(errorHandler()); return tagPageModel; } return Q_NULLPTR; } void AvailableNotePagesModel::addProject(const QString &, const Domain::DataSource::Ptr &) { qFatal("Not supported"); } void AvailableNotePagesModel::addContext(const QString &) { qFatal("Not supported"); } void AvailableNotePagesModel::addTag(const QString &name) { auto tag = Domain::Tag::Ptr::create(); tag->setName(name); const auto job = m_tagRepository->create(tag); installHandler(job, i18n("Cannot add tag %1", name)); } void AvailableNotePagesModel::removeItem(const QModelIndex &index) { QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); if (auto tag = object.objectCast()) { const auto job = m_tagRepository->remove(tag); installHandler(job, i18n("Cannot remove tag %1", tag->name())); } else { Q_ASSERT(false); } } QAbstractItemModel *AvailableNotePagesModel::createPageListModel() { m_inboxObject = QObjectPtr::create(); m_inboxObject->setProperty("name", i18n("Inbox")); m_tagsObject = QObjectPtr::create(); m_tagsObject->setProperty("name", i18n("Tags")); m_rootsProvider = Domain::QueryResultProvider::Ptr::create(); m_rootsProvider->append(m_inboxObject); m_rootsProvider->append(m_tagsObject); auto query = [this](const QObjectPtr &object) -> Domain::QueryResultInterface::Ptr { if (!object) return Domain::QueryResult::create(m_rootsProvider); else if (object == m_tagsObject) return Domain::QueryResult::copy(m_tagQueries->findAll()); else return Domain::QueryResult::Ptr(); }; auto flags = [this](const QObjectPtr &object) { const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled; const Qt::ItemFlags immutableNodeFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; const Qt::ItemFlags structureNodeFlags = Qt::NoItemFlags; return object.objectCast() ? defaultFlags : object == m_inboxObject ? immutableNodeFlags : structureNodeFlags; }; - auto data = [this](const QObjectPtr &object, int role) -> QVariant { + auto data = [this](const QObjectPtr &object, int role, int) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::DecorationRole && role != QueryTreeModelBase::IconNameRole) { return QVariant(); } if (role == Qt::EditRole && (object == m_inboxObject || object == m_tagsObject)) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return object->property("name").toString(); } else if (role == Qt::DecorationRole || role == QueryTreeModelBase::IconNameRole) { const QString iconName = (object == m_inboxObject) ? QStringLiteral("mail-folder-inbox") : (object == m_tagsObject) ? QStringLiteral("folder") : QStringLiteral("view-pim-tasks"); if (role == Qt::DecorationRole) return QVariant::fromValue(QIcon::fromTheme(iconName)); else return iconName; } else { return QVariant(); } }; auto setData = [this](const QObjectPtr &object, const QVariant &, int role) { if (role != Qt::EditRole) { return false; } if (object == m_inboxObject || object == m_tagsObject) { return false; } if (object.objectCast()) { return false; // Tag renaming is NOT allowed } else { Q_ASSERT(false); } return true; }; auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const QObjectPtr &object) { if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedArtifacts = mimeData->property("objects").value(); if (droppedArtifacts.isEmpty()) return false; if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), [](const Domain::Artifact::Ptr &droppedArtifact) { return !droppedArtifact.objectCast(); })) { return false; } if (auto tag = object.objectCast()) { foreach (const auto &droppedArtifact, droppedArtifacts) { auto note = droppedArtifact.staticCast(); const auto job = m_tagRepository->associate(tag, note); installHandler(job, i18n("Cannot tag %1 with %2", note->title(), tag->name())); } return true; } else if (object == m_inboxObject) { foreach (const auto &droppedArtifact, droppedArtifacts) { auto note = droppedArtifact.staticCast(); const auto job = m_tagRepository->dissociateAll(note); installHandler(job, i18n("Cannot move %1 to Inbox", note->title())); } return true; } return false; }; auto drag = [](const QObjectPtrList &) -> QMimeData* { return Q_NULLPTR; }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); } diff --git a/src/presentation/availablesourcesmodel.cpp b/src/presentation/availablesourcesmodel.cpp index 96e07316..1787ba48 100644 --- a/src/presentation/availablesourcesmodel.cpp +++ b/src/presentation/availablesourcesmodel.cpp @@ -1,156 +1,156 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "availablesourcesmodel.h" #include #include #include "domain/datasourcequeries.h" #include "domain/datasourcerepository.h" #include "presentation/querytreemodel.h" using namespace Presentation; AvailableSourcesModel::AvailableSourcesModel(const Domain::DataSourceQueries::Ptr &dataSourceQueries, const Domain::DataSourceRepository::Ptr &dataSourceRepository, QObject *parent) : QObject(parent), m_sourceListModel(Q_NULLPTR), m_dataSourceQueries(dataSourceQueries), m_dataSourceRepository(dataSourceRepository) { } QAbstractItemModel *AvailableSourcesModel::sourceListModel() { if (!m_sourceListModel) m_sourceListModel = createSourceListModel(); return m_sourceListModel; } void AvailableSourcesModel::showConfigDialog() { m_dataSourceRepository->showConfigDialog(); } QAbstractItemModel *AvailableSourcesModel::createSourceListModel() { auto query = [this] (const Domain::DataSource::Ptr &source) { if (!source) return m_dataSourceQueries->findTopLevel(); else return m_dataSourceQueries->findChildren(source); }; auto flags = [] (const Domain::DataSource::Ptr &source) -> Qt::ItemFlags { const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (source->contentTypes() != Domain::DataSource::NoContent) return defaultFlags | Qt::ItemIsUserCheckable; else return defaultFlags; }; - auto data = [this] (const Domain::DataSource::Ptr &source, int role) -> QVariant { + auto data = [this] (const Domain::DataSource::Ptr &source, int role, int) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::DecorationRole && role != Qt::CheckStateRole && role != QueryTreeModelBase::IconNameRole && role != QueryTreeModelBase::IsDefaultRole) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return source->name(); } else if (role == Qt::DecorationRole || role == QueryTreeModelBase::IconNameRole) { const QString iconName = source->iconName().isEmpty() ? QStringLiteral("folder") : source->iconName(); if (role == Qt::DecorationRole) return QVariant::fromValue(QIcon::fromTheme(iconName)); else return iconName; } else if (role == Qt::CheckStateRole) { if (source->contentTypes() != Domain::DataSource::NoContent) return source->isSelected() ? Qt::Checked : Qt::Unchecked; else return QVariant(); } else if (role == QueryTreeModelBase::IsDefaultRole) { return m_dataSourceQueries->isDefaultSource(source); } else { return QVariant(); } }; auto setData = [this] (const Domain::DataSource::Ptr &source, const QVariant &value, int role) { if (role != Qt::CheckStateRole) return false; if (source->contentTypes() == Domain::DataSource::NoContent) return false; source->setSelected(value.toInt() == Qt::Checked); const auto job = m_dataSourceRepository->update(source); installHandler(job, i18n("Cannot modify source %1", source->name())); return true; }; auto drop = [] (const QMimeData *mimeData, Qt::DropAction, const Domain::DataSource::Ptr &source) { Q_UNUSED(mimeData) Q_UNUSED(source) return false; }; auto drag = [](const Domain::DataSource::List &) -> QMimeData* { return Q_NULLPTR; }; connect(m_dataSourceQueries->notifier(), &Domain::DataSourceQueriesNotifier::defaultSourceChanged, this, &AvailableSourcesModel::onDefaultSourceChanged); - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); } void AvailableSourcesModel::setDefaultItem(const QModelIndex &index) { auto source = index.data(QueryTreeModelBase::ObjectRole).value(); Q_ASSERT(source); m_dataSourceQueries->setDefaultSource(source); } void AvailableSourcesModel::onDefaultSourceChanged() { emitDefaultSourceChanged(QModelIndex()); } void AvailableSourcesModel::emitDefaultSourceChanged(const QModelIndex &root) { const auto rowCount = m_sourceListModel->rowCount(root); for (int row = 0; row < rowCount; row++) { const auto index = m_sourceListModel->index(row, 0, root); emit m_sourceListModel->dataChanged(index, index); emitDefaultSourceChanged(index); } } diff --git a/src/presentation/availabletaskpagesmodel.cpp b/src/presentation/availabletaskpagesmodel.cpp index 8f3adb5d..8221085d 100644 --- a/src/presentation/availabletaskpagesmodel.cpp +++ b/src/presentation/availabletaskpagesmodel.cpp @@ -1,350 +1,350 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "availabletaskpagesmodel.h" #include #include #include #include "domain/contextqueries.h" #include "domain/contextrepository.h" #include "domain/projectqueries.h" #include "domain/projectrepository.h" #include "domain/taskrepository.h" #include "presentation/availablepagessortfilterproxymodel.h" #include "presentation/contextpagemodel.h" #include "presentation/metatypes.h" #include "presentation/projectpagemodel.h" #include "presentation/querytreemodel.h" #include "presentation/taskinboxpagemodel.h" #include "presentation/workdaypagemodel.h" #include "utils/jobhandler.h" #include "utils/datetime.h" using namespace Presentation; AvailableTaskPagesModel::AvailableTaskPagesModel(const Domain::DataSourceQueries::Ptr &dataSourceQueries, const Domain::ProjectQueries::Ptr &projectQueries, const Domain::ProjectRepository::Ptr &projectRepository, const Domain::ContextQueries::Ptr &contextQueries, const Domain::ContextRepository::Ptr &contextRepository, const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : AvailablePagesModelInterface(parent), m_pageListModel(Q_NULLPTR), m_sortProxyModel(Q_NULLPTR), m_dataSourceQueries(dataSourceQueries), m_projectQueries(projectQueries), m_projectRepository(projectRepository), m_contextQueries(contextQueries), m_contextRepository(contextRepository), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } QAbstractItemModel *AvailableTaskPagesModel::pageListModel() { if (!m_pageListModel) m_pageListModel = createPageListModel(); if (!m_sortProxyModel) { m_sortProxyModel = new AvailablePagesSortFilterProxyModel(this); m_sortProxyModel->setSourceModel(m_pageListModel); } return m_sortProxyModel; } bool AvailableTaskPagesModel::hasProjectPages() const { return true; } bool AvailableTaskPagesModel::hasContextPages() const { return true; } bool AvailableTaskPagesModel::hasTagPages() const { return false; } QObject *AvailableTaskPagesModel::createPageForIndex(const QModelIndex &index) { QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); if (object == m_inboxObject) { auto inboxPageModel = new TaskInboxPageModel(m_taskQueries, m_taskRepository, this); inboxPageModel->setErrorHandler(errorHandler()); return inboxPageModel; } else if (object == m_workdayObject) { auto workdayPageModel = new WorkdayPageModel(m_taskQueries, m_taskRepository, this); workdayPageModel->setErrorHandler(errorHandler()); return workdayPageModel; } else if (auto project = object.objectCast()) { auto projectPageModel = new ProjectPageModel(project, m_projectQueries, m_projectRepository, m_taskQueries, m_taskRepository, this); projectPageModel->setErrorHandler(errorHandler()); return projectPageModel; } else if (auto context = object.objectCast()) { auto contextPageModel = new ContextPageModel(context, m_contextQueries, m_contextRepository, m_taskQueries, m_taskRepository, this); contextPageModel->setErrorHandler(errorHandler()); return contextPageModel; } return Q_NULLPTR; } void AvailableTaskPagesModel::addProject(const QString &name, const Domain::DataSource::Ptr &source) { auto project = Domain::Project::Ptr::create(); project->setName(name); const auto job = m_projectRepository->create(project, source); installHandler(job, i18n("Cannot add project %1 in dataSource %2", name, source->name())); } void AvailableTaskPagesModel::addContext(const QString &name) { auto context = Domain::Context::Ptr::create(); context->setName(name); const auto job = m_contextRepository->create(context); installHandler(job, i18n("Cannot add context %1", name)); } void AvailableTaskPagesModel::addTag(const QString &) { qFatal("Not supported"); } void AvailableTaskPagesModel::removeItem(const QModelIndex &index) { QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); if (auto project = object.objectCast()) { const auto job = m_projectRepository->remove(project); installHandler(job, i18n("Cannot remove project %1", project->name())); } else if (auto context = object.objectCast()) { const auto job = m_contextRepository->remove(context); installHandler(job, i18n("Cannot remove context %1", context->name())); } else { Q_ASSERT(false); } } QAbstractItemModel *AvailableTaskPagesModel::createPageListModel() { m_inboxObject = QObjectPtr::create(); m_inboxObject->setProperty("name", i18n("Inbox")); m_workdayObject = QObjectPtr::create(); m_workdayObject->setProperty("name", i18n("Workday")); m_projectsObject = QObjectPtr::create(); m_projectsObject->setProperty("name", i18n("Projects")); m_contextsObject = QObjectPtr::create(); m_contextsObject->setProperty("name", i18n("Contexts")); m_rootsProvider = Domain::QueryResultProvider::Ptr::create(); m_rootsProvider->append(m_inboxObject); m_rootsProvider->append(m_workdayObject); m_rootsProvider->append(m_projectsObject); m_rootsProvider->append(m_contextsObject); auto query = [this](const QObjectPtr &object) -> Domain::QueryResultInterface::Ptr { if (!object) return Domain::QueryResult::create(m_rootsProvider); else if (object == m_projectsObject) return Domain::QueryResult::copy(m_dataSourceQueries->findAllSelected()); else if (object == m_contextsObject) return Domain::QueryResult::copy(m_contextQueries->findAll()); else if (const auto source = object.objectCast()) return Domain::QueryResult::copy(m_dataSourceQueries->findProjects(source)); else return Domain::QueryResult::Ptr(); }; auto flags = [this](const QObjectPtr &object) { const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled; const Qt::ItemFlags immutableNodeFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; const Qt::ItemFlags structureNodeFlags = Qt::NoItemFlags; return object.objectCast() ? defaultFlags : object.objectCast() ? defaultFlags : object == m_inboxObject ? immutableNodeFlags : object == m_workdayObject ? immutableNodeFlags : structureNodeFlags; }; - auto data = [this](const QObjectPtr &object, int role) -> QVariant { + auto data = [this](const QObjectPtr &object, int role, int) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::DecorationRole && role != QueryTreeModelBase::IconNameRole) { return QVariant(); } if (role == Qt::EditRole && (object == m_inboxObject || object == m_workdayObject || object == m_projectsObject || object == m_contextsObject || object.objectCast())) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return object->property("name").toString(); } else if (role == Qt::DecorationRole || role == QueryTreeModelBase::IconNameRole) { const QString iconName = object == m_inboxObject ? QStringLiteral("mail-folder-inbox") : (object == m_workdayObject) ? QStringLiteral("go-jump-today") : (object == m_projectsObject) ? QStringLiteral("folder") : (object == m_contextsObject) ? QStringLiteral("folder") : object.objectCast() ? QStringLiteral("folder") : object.objectCast() ? QStringLiteral("view-pim-notes") : QStringLiteral("view-pim-tasks"); if (role == Qt::DecorationRole) return QVariant::fromValue(QIcon::fromTheme(iconName)); else return iconName; } else { return QVariant(); } }; auto setData = [this](const QObjectPtr &object, const QVariant &value, int role) { if (role != Qt::EditRole) { return false; } if (object == m_inboxObject || object == m_workdayObject || object == m_projectsObject || object == m_contextsObject || object.objectCast()) { return false; } if (auto project = object.objectCast()) { const auto currentName = project->name(); project->setName(value.toString()); const auto job = m_projectRepository->update(project); installHandler(job, i18n("Cannot modify project %1", currentName)); } else if (auto context = object.objectCast()) { const auto currentName = context->name(); context->setName(value.toString()); const auto job = m_contextRepository->update(context); installHandler(job, i18n("Cannot modify context %1", currentName)); } else { Q_ASSERT(false); } return true; }; auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const QObjectPtr &object) { if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedArtifacts = mimeData->property("objects").value(); if (droppedArtifacts.isEmpty()) return false; if (auto project = object.objectCast()) { foreach (const auto &droppedArtifact, droppedArtifacts) { const auto job = m_projectRepository->associate(project, droppedArtifact); installHandler(job, i18n("Cannot add %1 to project %2", droppedArtifact->title(), project->name())); } return true; } else if (auto context = object.objectCast()) { if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), [](const Domain::Artifact::Ptr &droppedArtifact) { return !droppedArtifact.objectCast(); })) { return false; } foreach (const auto &droppedArtifact, droppedArtifacts) { auto task = droppedArtifact.staticCast(); const auto job = m_contextRepository->associate(context, task); installHandler(job, i18n("Cannot add %1 to context %2", task->title(), context->name())); } return true; } else if (object == m_inboxObject) { foreach (const auto &droppedArtifact, droppedArtifacts) { const auto job = m_projectRepository->dissociate(droppedArtifact); installHandler(job, i18n("Cannot move %1 to Inbox", droppedArtifact->title())); if (auto task = droppedArtifact.objectCast()) { Utils::JobHandler::install(job, [this, task] { const auto dissociateJob = m_taskRepository->dissociateAll(task); installHandler(dissociateJob, i18n("Cannot move task %1 to Inbox", task->title())); }); } } return true; } else if (object == m_workdayObject) { foreach (const auto &droppedArtifact, droppedArtifacts) { if (auto task = droppedArtifact.objectCast()) { task->setStartDate(Utils::DateTime::currentDate()); const auto job = m_taskRepository->update(task); installHandler(job, i18n("Cannot update task %1 to Workday", task->title())); } } return true; } return false; }; auto drag = [](const QObjectPtrList &) -> QMimeData* { return Q_NULLPTR; }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); } diff --git a/src/presentation/contextpagemodel.cpp b/src/presentation/contextpagemodel.cpp index 7954620e..3c451b24 100644 --- a/src/presentation/contextpagemodel.cpp +++ b/src/presentation/contextpagemodel.cpp @@ -1,207 +1,207 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 RĂ©mi Benoit 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "contextpagemodel.h" #include #include #include "domain/task.h" #include "domain/contextqueries.h" #include "domain/contextrepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/querytreemodel.h" using namespace Presentation; ContextPageModel::ContextPageModel(const Domain::Context::Ptr &context, const Domain::ContextQueries::Ptr &contextQueries, const Domain::ContextRepository::Ptr &contextRepository, const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : PageModel(parent), m_context(context), m_contextQueries(contextQueries), m_contextRepository(contextRepository), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } Domain::Context::Ptr ContextPageModel::context() const { return m_context; } Domain::Artifact::Ptr ContextPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { const auto parentData = parentIndex.data(QueryTreeModelBase::ObjectRole); const auto parentArtifact = parentData.value(); const auto parentTask = parentArtifact.objectCast(); auto task = Domain::Task::Ptr::create(); task->setTitle(title); const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) : m_taskRepository->createInContext(task, m_context); installHandler(job, i18n("Cannot add task %1 in context %2", title, m_context->name())); return task; } void ContextPageModel::removeItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); const auto job = index.parent().isValid() ? m_taskRepository->dissociate(task) : m_contextRepository->dissociate(m_context, task); installHandler(job, i18n("Cannot remove task %1 from context %2", task->title(), m_context->name())); } void ContextPageModel::promoteItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); Q_ASSERT(task); const auto job = m_taskRepository->promoteToProject(task); installHandler(job, i18n("Cannot promote task %1 to be a project", task->title())); } QAbstractItemModel *ContextPageModel::createCentralListModel() { auto query = [this] (const Domain::Task::Ptr &task) -> Domain::QueryResultInterface::Ptr { if (!task) return m_contextQueries->findTopLevelTasks(m_context); else return m_taskQueries->findChildren(task); }; auto flags = [] (const Domain::Task::Ptr &task) { Q_UNUSED(task); return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled; }; - auto data = [] (const Domain::Task::Ptr &task, int role) -> QVariant { + auto data = [] (const Domain::Task::Ptr &task, int role, int) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::CheckStateRole) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return task->title(); } else if (role == Qt::CheckStateRole){ return task->isDone() ? Qt::Checked : Qt::Unchecked; } else { return QVariant(); } }; auto setData = [this] (const Domain::Task::Ptr &task, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) return false; const auto currentTitle = task->title(); if (role == Qt::EditRole) task->setTitle(value.toString()); else task->setDone(value.toInt() == Qt::Checked); const auto job = m_taskRepository->update(task); installHandler(job, i18n("Cannot modify task %1 in context %2", currentTitle, m_context->name())); return true; }; auto drop = [this] (const QMimeData *mimeData, Qt::DropAction, const Domain::Task::Ptr &parentTask) { if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedArtifacts = mimeData->property("objects").value(); if (droppedArtifacts.isEmpty()) return false; if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), [](const Domain::Artifact::Ptr &droppedArtifact) { return !droppedArtifact.objectCast(); })) { return false; } using namespace std::placeholders; auto associate = std::function(); auto dissociate = std::function(); auto parentTitle = QString(); if (parentTask) { associate = std::bind(&Domain::TaskRepository::associate, m_taskRepository, parentTask, _1); dissociate = [] (Domain::Task::Ptr) -> KJob* { return Q_NULLPTR; }; parentTitle = parentTask->title(); } else { associate = std::bind(&Domain::ContextRepository::associate, m_contextRepository, m_context, _1); dissociate = std::bind(&Domain::TaskRepository::dissociate, m_taskRepository, _1); parentTitle = m_context->name(); } foreach(const Domain::Artifact::Ptr &droppedArtifact, droppedArtifacts) { auto childTask = droppedArtifact.objectCast(); auto job = associate(childTask); installHandler(job, i18n("Cannot move task %1 as sub-task of %2", childTask->title(), parentTitle)); job = dissociate(childTask); if (job) installHandler(job, i18n("Cannot dissociate task %1 from its parent", childTask->title())); } return true; }; auto drag = [] (const Domain::Task::List &tasks) -> QMimeData* { if (tasks.isEmpty()) return Q_NULLPTR; auto draggedArtifacts = Domain::Artifact::List(); draggedArtifacts.reserve(tasks.size()); foreach (const Domain::Task::Ptr &task, tasks) { draggedArtifacts.append(task.objectCast()); } auto data = new QMimeData(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(draggedArtifacts)); return data; }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); } diff --git a/src/presentation/noteinboxpagemodel.cpp b/src/presentation/noteinboxpagemodel.cpp index 2516f40b..06afa28d 100644 --- a/src/presentation/noteinboxpagemodel.cpp +++ b/src/presentation/noteinboxpagemodel.cpp @@ -1,125 +1,125 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "noteinboxpagemodel.h" #include #include #include "presentation/querytreemodel.h" using namespace Presentation; NoteInboxPageModel::NoteInboxPageModel(const Domain::NoteQueries::Ptr ¬eQueries, const Domain::NoteRepository::Ptr ¬eRepository, QObject *parent) : PageModel(parent), m_noteQueries(noteQueries), m_noteRepository(noteRepository) { } Domain::Artifact::Ptr NoteInboxPageModel::addItem(const QString &title, const QModelIndex &) { auto note = Domain::Note::Ptr::create(); note->setTitle(title); const auto job = m_noteRepository->create(note); installHandler(job, i18n("Cannot add note %1 in Inbox", title)); return note; } void NoteInboxPageModel::removeItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto note = artifact.objectCast(); Q_ASSERT(note); const auto job = m_noteRepository->remove(note); installHandler(job, i18n("Cannot remove note %1 from Inbox", note->title())); } void NoteInboxPageModel::promoteItem(const QModelIndex &) { qFatal("Not supported"); } QAbstractItemModel *NoteInboxPageModel::createCentralListModel() { auto query = [this](const Domain::Note::Ptr ¬e) -> Domain::QueryResultInterface::Ptr { if (!note) return m_noteQueries->findInbox(); else return Domain::QueryResult::Ptr(); }; auto flags = [](const Domain::Note::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; }; - auto data = [](const Domain::Note::Ptr ¬e, int role) -> QVariant { + auto data = [](const Domain::Note::Ptr ¬e, int role, int) -> QVariant { if (role == Qt::DisplayRole || role == Qt::EditRole) { return note->title(); } else { return QVariant(); } }; auto setData = [this](const Domain::Note::Ptr ¬e, const QVariant &value, int role) { if (role != Qt::EditRole) { return false; } const auto currentTitle = note->title(); note->setTitle(value.toString()); const auto job = m_noteRepository->update(note); installHandler(job, i18n("Cannot modify note %1 in Inbox", currentTitle)); return true; }; auto drop = [](const QMimeData *, Qt::DropAction, const Domain::Artifact::Ptr &) { return false; }; auto drag = [](const Domain::Note::List ¬es) -> QMimeData* { if (notes.isEmpty()) return Q_NULLPTR; auto artifacts = Domain::Artifact::List(); artifacts.reserve(notes.size()); std::copy(notes.constBegin(), notes.constEnd(), std::back_inserter(artifacts)); auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(artifacts)); return data; }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); } diff --git a/src/presentation/projectpagemodel.cpp b/src/presentation/projectpagemodel.cpp index 2d79da1a..0c0bbc41 100644 --- a/src/presentation/projectpagemodel.cpp +++ b/src/presentation/projectpagemodel.cpp @@ -1,192 +1,192 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "projectpagemodel.h" #include #include #include "presentation/querytreemodel.h" using namespace Presentation; ProjectPageModel::ProjectPageModel(const Domain::Project::Ptr &project, const Domain::ProjectQueries::Ptr &projectQueries, const Domain::ProjectRepository::Ptr &projectRepository, const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : PageModel(parent), m_projectQueries(projectQueries), m_projectRepository(projectRepository), m_project(project), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } Domain::Project::Ptr ProjectPageModel::project() const { return m_project; } Domain::Artifact::Ptr ProjectPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { const auto parentData = parentIndex.data(QueryTreeModelBase::ObjectRole); const auto parentArtifact = parentData.value(); const auto parentTask = parentArtifact.objectCast(); auto task = Domain::Task::Ptr::create(); task->setTitle(title); const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) : m_taskRepository->createInProject(task, m_project); installHandler(job, i18n("Cannot add task %1 in project %2", title, m_project->name())); return task; } void ProjectPageModel::removeItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); Q_ASSERT(task); const auto job = m_taskRepository->remove(task); installHandler(job, i18n("Cannot remove task %1 from project %2", task->title(), m_project->name())); } void ProjectPageModel::promoteItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); Q_ASSERT(task); const auto job = m_taskRepository->promoteToProject(task); installHandler(job, i18n("Cannot promote task %1 to be a project", task->title())); } QAbstractItemModel *ProjectPageModel::createCentralListModel() { auto query = [this](const Domain::Task::Ptr &task) -> Domain::QueryResultInterface::Ptr { if (!task) return m_projectQueries->findTopLevel(m_project); else return m_taskQueries->findChildren(task); }; auto flags = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled; }; - auto data = [](const Domain::Task::Ptr &task, int role) -> QVariant { + auto data = [](const Domain::Task::Ptr &task, int role, int) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::CheckStateRole) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return task->title(); } else { return task->isDone() ? Qt::Checked : Qt::Unchecked; } }; auto setData = [this](const Domain::Task::Ptr &task, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) { return false; } const auto currentTitle = task->title(); if (role == Qt::EditRole) task->setTitle(value.toString()); else task->setDone(value.toInt() == Qt::Checked); const auto job = m_taskRepository->update(task); installHandler(job, i18n("Cannot modify task %1 in project %2", currentTitle, m_project->name())); return true; }; auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const Domain::Task::Ptr &parentTask) { if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedArtifacts = mimeData->property("objects").value(); if (droppedArtifacts.isEmpty()) return false; if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), [](const Domain::Artifact::Ptr &droppedArtifact) { return !droppedArtifact.objectCast(); })) { return false; } using namespace std::placeholders; auto associate = std::function(); auto parentTitle = QString(); if (parentTask) { associate = std::bind(&Domain::TaskRepository::associate, m_taskRepository, parentTask, _1); parentTitle = parentTask->title(); } else { associate = std::bind(&Domain::ProjectRepository::associate, m_projectRepository, m_project, _1); parentTitle = m_project->name(); } foreach(const Domain::Artifact::Ptr &droppedArtifact, droppedArtifacts) { auto childTask = droppedArtifact.objectCast(); const auto job = associate(childTask); installHandler(job, i18n("Cannot move task %1 as a sub-task of %2", childTask->title(), parentTitle)); } return true; }; auto drag = [](const Domain::Task::List &tasks) -> QMimeData* { if (tasks.isEmpty()) return Q_NULLPTR; auto draggedArtifacts = Domain::Artifact::List(); draggedArtifacts.reserve(tasks.size()); foreach (const Domain::Task::Ptr &task, tasks) { draggedArtifacts.append(task.objectCast()); } auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(draggedArtifacts)); return data; }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); } diff --git a/src/presentation/querytreemodel.h b/src/presentation/querytreemodel.h index 47fd95c4..2ae640be 100644 --- a/src/presentation/querytreemodel.h +++ b/src/presentation/querytreemodel.h @@ -1,97 +1,122 @@ /* This file is part of Zanshin Copyright 2014 Mario Bensi Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef PRESENTATION_QUERYTREEMODEL_H #define PRESENTATION_QUERYTREEMODEL_H #include "querytreenode.h" #include #include namespace Presentation { -template +template class QueryTreeModel : public QueryTreeModelBase { public: - typedef typename QueryTreeNode::QueryGenerator QueryGenerator; - typedef typename QueryTreeNode::FlagsFunction FlagsFunction; - typedef typename QueryTreeNode::DataFunction DataFunction; - typedef typename QueryTreeNode::SetDataFunction SetDataFunction; - typedef typename QueryTreeNode::DropFunction DropFunction; + typedef typename QueryTreeNode::QueryGenerator QueryGenerator; + typedef typename QueryTreeNode::FlagsFunction FlagsFunction; + typedef typename QueryTreeNode::DataFunction DataFunction; + typedef typename QueryTreeNode::SetDataFunction SetDataFunction; + typedef typename QueryTreeNode::DropFunction DropFunction; typedef std::function &)> DragFunction; + using FetchAdditionalInfoFunction = std::function; + using NodeType = QueryTreeNode; explicit QueryTreeModel(const QueryGenerator &queryGenerator, const FlagsFunction &flagsFunction, const DataFunction &dataFunction, const SetDataFunction &setDataFunction, QObject *parent = Q_NULLPTR) - : QueryTreeModelBase(new QueryTreeNode(ItemType(), Q_NULLPTR, this, + : QueryTreeModelBase(new QueryTreeNode(ItemType(), Q_NULLPTR, this, queryGenerator, flagsFunction, dataFunction, setDataFunction), parent) { } explicit QueryTreeModel(const QueryGenerator &queryGenerator, const FlagsFunction &flagsFunction, const DataFunction &dataFunction, const SetDataFunction &setDataFunction, const DropFunction &dropFunction, const DragFunction &dragFunction, + const FetchAdditionalInfoFunction &fetchAdditionalInfoFunction, QObject *parent = Q_NULLPTR) - : QueryTreeModelBase(new QueryTreeNode(ItemType(), Q_NULLPTR, this, + : QueryTreeModelBase(new QueryTreeNode(ItemType(), Q_NULLPTR, this, queryGenerator, flagsFunction, dataFunction, setDataFunction, dropFunction), parent), - m_dragFunction(dragFunction) + m_dragFunction(dragFunction), + m_fetchAdditionalInfoFunction(fetchAdditionalInfoFunction) { } protected: QMimeData *createMimeData(const QModelIndexList &indexes) const Q_DECL_OVERRIDE { if (m_dragFunction) { QList items; std::transform(indexes.begin(), indexes.end(), std::back_inserter(items), [this](const QModelIndex &index) { - return static_cast*>(nodeFromIndex(index))->item(); + return itemAtIndex(index); }); return m_dragFunction(items); } else { return Q_NULLPTR; } } + + void fetchAdditionalInfo(const QModelIndex &index) override + { + if (m_fetchAdditionalInfoFunction) { + auto theNode = node(index); + if (!theNode->hasAdditionalInfo()) + theNode->setAdditionalInfo(m_fetchAdditionalInfoFunction(index, theNode->item())); + } + } + + ItemType itemAtIndex(const QModelIndex &index) const + { + return node(index)->item(); + } + + NodeType *node(const QModelIndex &index) const + { + return static_cast(nodeFromIndex(index)); + } + private: DragFunction m_dragFunction; + FetchAdditionalInfoFunction m_fetchAdditionalInfoFunction; }; } #endif // PRESENTATION_QUERYTREEMODEL_H diff --git a/src/presentation/querytreemodelbase.cpp b/src/presentation/querytreemodelbase.cpp index 4b6a48c9..e1b30239 100644 --- a/src/presentation/querytreemodelbase.cpp +++ b/src/presentation/querytreemodelbase.cpp @@ -1,267 +1,269 @@ /* This file is part of Zanshin Copyright 2014 Mario Bensi Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "querytreemodelbase.h" #include #include #include using namespace Presentation; QueryTreeNodeBase::QueryTreeNodeBase(QueryTreeNodeBase *parent, QueryTreeModelBase *model) : m_parent(parent), m_model(model) { } QueryTreeNodeBase::~QueryTreeNodeBase() { qDeleteAll(m_childNode); } int QueryTreeNodeBase::row() { return m_parent ? m_parent->m_childNode.indexOf(this) : -1; } QueryTreeNodeBase *QueryTreeNodeBase::parent() const { return m_parent; } QueryTreeNodeBase *QueryTreeNodeBase::child(int row) const { if (row >= 0 && row < m_childNode.size()) return m_childNode.value(row); else return Q_NULLPTR; } void QueryTreeNodeBase::insertChild(int row, QueryTreeNodeBase *node) { m_childNode.insert(row, node); } void QueryTreeNodeBase::appendChild(QueryTreeNodeBase *node) { m_childNode.append(node); } void QueryTreeNodeBase::removeChildAt(int row) { delete m_childNode.takeAt(row); } int QueryTreeNodeBase::childCount() const { return m_childNode.size(); } QModelIndex QueryTreeNodeBase::index(int row, int column, const QModelIndex &parent) const { return m_model->index(row, column, parent); } QModelIndex QueryTreeNodeBase::createIndex(int row, int column, void *data) const { return m_model->createIndex(row, column, data); } void QueryTreeNodeBase::beginInsertRows(const QModelIndex &parent, int first, int last) { m_model->beginInsertRows(parent, first, last); } void QueryTreeNodeBase::endInsertRows() { m_model->endInsertRows(); } void QueryTreeNodeBase::beginRemoveRows(const QModelIndex &parent, int first, int last) { m_model->beginRemoveRows(parent, first, last); } void QueryTreeNodeBase::endRemoveRows() { m_model->endRemoveRows(); } void QueryTreeNodeBase::emitDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { emit m_model->dataChanged(topLeft, bottomRight); } QueryTreeModelBase::QueryTreeModelBase(QueryTreeNodeBase *rootNode, QObject *parent) : QAbstractItemModel(parent), m_rootIndexFlag(Qt::ItemIsDropEnabled), m_rootNode(rootNode) { auto roles = roleNames(); roles.insert(ObjectRole, "object"); roles.insert(IconNameRole, "icon"); roles.insert(IsDefaultRole, "default"); setRoleNames(roles); } QueryTreeModelBase::~QueryTreeModelBase() { delete m_rootNode; } Qt::ItemFlags QueryTreeModelBase::flags(const QModelIndex &index) const { if (!isModelIndexValid(index)) return m_rootIndexFlag; return nodeFromIndex(index)->flags(); } QModelIndex QueryTreeModelBase::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0) return QModelIndex(); const QueryTreeNodeBase *parentNode = nodeFromIndex(parent); if (row < parentNode->childCount()) { QueryTreeNodeBase *node = parentNode->child(row); return createIndex(row, column, node); } else { return QModelIndex(); } } QModelIndex QueryTreeModelBase::parent(const QModelIndex &index) const { QueryTreeNodeBase *node = nodeFromIndex(index); if (!node->parent() || node->parent() == m_rootNode) return QModelIndex(); else return createIndex(node->parent()->row(), 0, node->parent()); } int QueryTreeModelBase::rowCount(const QModelIndex &index) const { return nodeFromIndex(index)->childCount(); } int QueryTreeModelBase::columnCount(const QModelIndex &) const { return 1; } QVariant QueryTreeModelBase::data(const QModelIndex &index, int role) const { if (!isModelIndexValid(index)) { return QVariant(); } + const_cast(this)->fetchAdditionalInfo(index); + return nodeFromIndex(index)->data(role); } bool QueryTreeModelBase::setData(const QModelIndex &index, const QVariant &value, int role) { if (!isModelIndexValid(index)) { return false; } return nodeFromIndex(index)->setData(value, role); } bool QueryTreeModelBase::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); // If that's not holding that mime type we can't do the cycle checking // this is relevant only for internal drag and drop anyway if (data->hasFormat(QStringLiteral("application/x-zanshin-indexes"))) { const auto indexes = data->property("indexes").value(); foreach (const auto &index, indexes) { auto p = parent; while (p.isValid()) { if (p == index) // Oops, we found a cycle (one of the indexes is parent of the drop point) return false; p = p.parent(); } } } return nodeFromIndex(parent)->dropMimeData(data, action); } QMimeData *QueryTreeModelBase::mimeData(const QModelIndexList &indexes) const { if (indexes.isEmpty()) return Q_NULLPTR; auto data = createMimeData(indexes); data->setData(QStringLiteral("application/x-zanshin-indexes"), "indexes"); data->setProperty("indexes", QVariant::fromValue(indexes)); return data; } QStringList QueryTreeModelBase::mimeTypes() const { return QAbstractItemModel::mimeTypes() << QStringLiteral("application/x-zanshin-object") << QStringLiteral("application/x-zanshin-indexes"); } Qt::DropActions QueryTreeModelBase::supportedDragActions() const { return Qt::MoveAction; } Qt::DropActions QueryTreeModelBase::supportedDropActions() const { return Qt::MoveAction; } QueryTreeNodeBase *QueryTreeModelBase::nodeFromIndex(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : m_rootNode; } void QueryTreeModelBase::setRootIndexFlag(Qt::ItemFlags flags) { m_rootIndexFlag = flags; } bool QueryTreeModelBase::isModelIndexValid(const QModelIndex &index) const { bool valid = index.isValid() && index.column() == 0 && index.row() >= 0; if (!valid) return false; const QueryTreeNodeBase *parentNode = nodeFromIndex(index.parent()); const int count = parentNode->childCount(); return index.row() < count; } diff --git a/src/presentation/querytreemodelbase.h b/src/presentation/querytreemodelbase.h index eb90f35e..d497e6b3 100644 --- a/src/presentation/querytreemodelbase.h +++ b/src/presentation/querytreemodelbase.h @@ -1,112 +1,114 @@ /* This file is part of Zanshin Copyright 2014 Mario Bensi Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef PRESENTATION_QUERYTREEMODELBASE_H #define PRESENTATION_QUERYTREEMODELBASE_H #include namespace Presentation { class QueryTreeModelBase; class QueryTreeNodeBase { public: QueryTreeNodeBase(QueryTreeNodeBase *parent, QueryTreeModelBase *model); virtual ~QueryTreeNodeBase(); virtual Qt::ItemFlags flags() const = 0; virtual QVariant data(int role) const = 0; virtual bool setData(const QVariant &value, int role) = 0; virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action) = 0; int row(); QueryTreeNodeBase *parent() const; QueryTreeNodeBase *child(int row) const; void insertChild(int row, QueryTreeNodeBase *node); void appendChild(QueryTreeNodeBase *node); void removeChildAt(int row); int childCount() const; protected: QModelIndex index(int row, int column, const QModelIndex &parent) const; QModelIndex createIndex(int row, int column, void *data) const; void beginInsertRows(const QModelIndex &parent, int first, int last); void endInsertRows(); void beginRemoveRows(const QModelIndex &parent, int first, int last); void endRemoveRows(); void emitDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); private: QueryTreeNodeBase *m_parent; QList m_childNode; QueryTreeModelBase *m_model; }; class QueryTreeModelBase : public QAbstractItemModel { Q_OBJECT public: enum { ObjectRole = Qt::UserRole + 1, IconNameRole, IsDefaultRole, + AdditionalInfoRole, UserRole }; ~QueryTreeModelBase(); Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) Q_DECL_OVERRIDE; QMimeData *mimeData(const QModelIndexList &indexes) const Q_DECL_OVERRIDE; QStringList mimeTypes() const Q_DECL_OVERRIDE; Qt::DropActions supportedDragActions() const Q_DECL_OVERRIDE; Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE; protected: explicit QueryTreeModelBase(QueryTreeNodeBase *rootNode, QObject *parent = Q_NULLPTR); virtual QMimeData *createMimeData(const QModelIndexList &indexes) const = 0; + virtual void fetchAdditionalInfo(const QModelIndex &) {} QueryTreeNodeBase *nodeFromIndex(const QModelIndex &index) const; void setRootIndexFlag(Qt::ItemFlags flags); private: friend class QueryTreeNodeBase; bool isModelIndexValid(const QModelIndex &index) const; Qt::ItemFlags m_rootIndexFlag; QueryTreeNodeBase *m_rootNode; }; } #endif // PRESENTATION_QUERYTREEMODELBASE_H diff --git a/src/presentation/querytreenode.h b/src/presentation/querytreenode.h index 4c7bb4b6..77705599 100644 --- a/src/presentation/querytreenode.h +++ b/src/presentation/querytreenode.h @@ -1,182 +1,187 @@ /* This file is part of Zanshin Copyright 2014 Mario Bensi Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef PRESENTATION_QUERYTREENODE_H #define PRESENTATION_QUERYTREENODE_H #include // Qt5 TODO, shouldn't be needed anymore, QVariant will do the right thing #include "domain/note.h" #include "domain/task.h" #include "domain/queryresultinterface.h" #include "querytreemodelbase.h" namespace Presentation { // Qt5 TODO, shouldn't be needed anymore, QVariant will do the right thing namespace Internal { template QVariant variantFromValue(const T &object) { return QVariant::fromValue(object); } template<> inline QVariant variantFromValue(const Domain::Note::Ptr &task) { return QVariant::fromValue(task.staticCast()); } template<> inline QVariant variantFromValue(const Domain::Task::Ptr ¬e) { return QVariant::fromValue(note.staticCast()); } } -template +template class QueryTreeNode : public QueryTreeNodeBase { public: typedef Domain::QueryResultInterface ItemQuery; typedef typename ItemQuery::Ptr ItemQueryPtr; typedef std::function QueryGenerator; typedef std::function FlagsFunction; - typedef std::function DataFunction; + typedef std::function DataFunction; typedef std::function SetDataFunction; typedef std::function DropFunction; QueryTreeNode(const ItemType &item, QueryTreeNodeBase *parentNode, QueryTreeModelBase *model, const QueryGenerator &queryGenerator, const FlagsFunction &flagsFunction, const DataFunction &dataFunction, const SetDataFunction &setDataFunction) : QueryTreeNodeBase(parentNode, model), m_item(item), m_flagsFunction(flagsFunction), m_dataFunction(dataFunction), m_setDataFunction(setDataFunction) { init(model, queryGenerator); } QueryTreeNode(const ItemType &item, QueryTreeNodeBase *parentNode, QueryTreeModelBase *model, const QueryGenerator &queryGenerator, const FlagsFunction &flagsFunction, const DataFunction &dataFunction, const SetDataFunction &setDataFunction, const DropFunction &dropFunction) : QueryTreeNodeBase(parentNode, model), m_item(item), m_flagsFunction(flagsFunction), m_dataFunction(dataFunction), m_setDataFunction(setDataFunction), m_dropFunction(dropFunction) { init(model, queryGenerator); } ItemType item() const { return m_item; } Qt::ItemFlags flags() const Q_DECL_OVERRIDE { return m_flagsFunction(m_item); } QVariant data(int role) const Q_DECL_OVERRIDE { if (role == QueryTreeModelBase::ObjectRole) return Internal::variantFromValue(m_item); - return m_dataFunction(m_item, role); + return m_dataFunction(m_item, role, m_additionalInfo); } bool setData(const QVariant &value, int role) Q_DECL_OVERRIDE { return m_setDataFunction(m_item, value, role); } bool dropMimeData(const QMimeData *data, Qt::DropAction action) Q_DECL_OVERRIDE { if (m_dropFunction) return m_dropFunction(data, action, m_item); else return false; } + bool hasAdditionalInfo() const { return m_additionalInfo; } + void setAdditionalInfo(const AdditionalInfo &info) { m_additionalInfo = info; } + private: void init(QueryTreeModelBase *model, const QueryGenerator &queryGenerator) { m_children = queryGenerator(m_item); if (!m_children) return; for (auto child : m_children->data()) { - QueryTreeNodeBase *node = new QueryTreeNode(child, this, + QueryTreeNodeBase *node = new QueryTreeNode(child, this, model, queryGenerator, m_flagsFunction, m_dataFunction, m_setDataFunction, m_dropFunction); appendChild(node); } m_children->addPreInsertHandler([this](const ItemType &, int index) { QModelIndex parentIndex = parent() ? createIndex(row(), 0, this) : QModelIndex(); beginInsertRows(parentIndex, index, index); }); m_children->addPostInsertHandler([this, model, queryGenerator](const ItemType &item, int index) { - QueryTreeNodeBase *node = new QueryTreeNode(item, this, + QueryTreeNodeBase *node = new QueryTreeNode(item, this, model, queryGenerator, m_flagsFunction, m_dataFunction, m_setDataFunction, m_dropFunction); insertChild(index, node); endInsertRows(); }); m_children->addPreRemoveHandler([this](const ItemType &, int index) { QModelIndex parentIndex = parent() ? createIndex(row(), 0, this) : QModelIndex(); beginRemoveRows(parentIndex, index, index); }); m_children->addPostRemoveHandler([this](const ItemType &, int index) { removeChildAt(index); endRemoveRows(); }); m_children->addPostReplaceHandler([this](const ItemType &, int idx) { - QModelIndex parentIndex = parent() ? createIndex(row(), 0, this) : QModelIndex(); - emitDataChanged(index(idx, 0, parentIndex), index(idx, 0, parentIndex)); + const QModelIndex parentIndex = parent() ? createIndex(row(), 0, this) : QModelIndex(); + const QModelIndex dataIndex = index(idx, 0, parentIndex); + emitDataChanged(dataIndex, dataIndex); }); } ItemType m_item; ItemQueryPtr m_children; + mutable AdditionalInfo m_additionalInfo; FlagsFunction m_flagsFunction; DataFunction m_dataFunction; SetDataFunction m_setDataFunction; DropFunction m_dropFunction; }; } #endif // PRESENTATION_QUERYTREENODE_H diff --git a/src/presentation/tagpagemodel.cpp b/src/presentation/tagpagemodel.cpp index 62c49710..320fcce4 100644 --- a/src/presentation/tagpagemodel.cpp +++ b/src/presentation/tagpagemodel.cpp @@ -1,146 +1,146 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 RĂ©mi Benoit 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "tagpagemodel.h" #include #include #include "domain/noterepository.h" #include "domain/task.h" #include "domain/tagqueries.h" #include "domain/tagrepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/querytreemodel.h" using namespace Presentation; TagPageModel::TagPageModel(const Domain::Tag::Ptr &tag, const Domain::TagQueries::Ptr &tagQueries, const Domain::TagRepository::Ptr &tagRepository, const Domain::NoteRepository::Ptr ¬eRepository, QObject *parent) : PageModel(parent), m_tag(tag), m_tagQueries(tagQueries), m_tagRepository(tagRepository), m_noteRepository(noteRepository) { } Domain::Tag::Ptr TagPageModel::tag() const { return m_tag; } Domain::Artifact::Ptr TagPageModel::addItem(const QString &title, const QModelIndex &) { auto note = Domain::Note::Ptr::create(); note->setTitle(title); const auto job = m_noteRepository->createInTag(note, m_tag); installHandler(job, i18n("Cannot add note %1 in tag %2", title, m_tag->name())); return note; } void TagPageModel::removeItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto note = artifact.objectCast(); Q_ASSERT(note); const auto job = m_tagRepository->dissociate(m_tag, note); installHandler(job, i18n("Cannot remove note %1 from tag %2", note->title(), m_tag->name())); } void TagPageModel::promoteItem(const QModelIndex &) { qFatal("Not supported"); } QAbstractItemModel *TagPageModel::createCentralListModel() { auto query = [this] (const Domain::Note::Ptr ¬e) -> Domain::QueryResultInterface::Ptr { if (!note) return m_tagQueries->findNotes(m_tag); else return Domain::QueryResult::Ptr(); }; auto flags = [](const Domain::Note::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; }; - auto data = [](const Domain::Note::Ptr ¬e, int role) -> QVariant { + auto data = [](const Domain::Note::Ptr ¬e, int role, int) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return note->title(); } else { return QVariant(); } }; auto setData = [this] (const Domain::Note::Ptr ¬e, const QVariant &value, int role) { if (role != Qt::EditRole) { return false; } const auto currentTitle = note->title(); note->setTitle(value.toString()); const auto job = m_noteRepository->update(note); installHandler(job, i18n("Cannot modify note %1 in tag %2", currentTitle, m_tag->name())); return true; }; auto drop = [] (const QMimeData *, Qt::DropAction, const Domain::Note::Ptr &) { return false; }; auto drag = [] (const Domain::Note::List ¬es) -> QMimeData* { if (notes.isEmpty()) return Q_NULLPTR; auto draggedArtifacts = Domain::Artifact::List(); draggedArtifacts.reserve(notes.count()); std::copy(notes.constBegin(), notes.constEnd(), std::back_inserter(draggedArtifacts)); auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(draggedArtifacts)); return data; }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); } diff --git a/src/presentation/taskinboxpagemodel.cpp b/src/presentation/taskinboxpagemodel.cpp index 9099586d..7beaa3de 100644 --- a/src/presentation/taskinboxpagemodel.cpp +++ b/src/presentation/taskinboxpagemodel.cpp @@ -1,176 +1,176 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "taskinboxpagemodel.h" #include #include #include "presentation/querytreemodel.h" using namespace Presentation; TaskInboxPageModel::TaskInboxPageModel(const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : PageModel(parent), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } Domain::Artifact::Ptr TaskInboxPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { const auto parentData = parentIndex.data(QueryTreeModelBase::ObjectRole); const auto parentArtifact = parentData.value(); const auto parentTask = parentArtifact.objectCast(); auto task = Domain::Task::Ptr::create(); task->setTitle(title); const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) : m_taskRepository->create(task); installHandler(job, i18n("Cannot add task %1 in Inbox", title)); return task; } void TaskInboxPageModel::removeItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); Q_ASSERT(task); const auto job = m_taskRepository->remove(task); installHandler(job, i18n("Cannot remove task %1 from Inbox", task->title())); } void TaskInboxPageModel::promoteItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); Q_ASSERT(task); const auto job = m_taskRepository->promoteToProject(task); installHandler(job, i18n("Cannot promote task %1 to be a project", task->title())); } QAbstractItemModel *TaskInboxPageModel::createCentralListModel() { auto query = [this](const Domain::Task::Ptr &task) -> Domain::QueryResultInterface::Ptr { if (!task) return m_taskQueries->findInboxTopLevel(); else return m_taskQueries->findChildren(task); }; auto flags = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled; }; - auto data = [](const Domain::Task::Ptr &task, int role) -> QVariant { + auto data = [](const Domain::Task::Ptr &task, int role, int) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::CheckStateRole) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return task->title(); } else { return task->isDone() ? Qt::Checked : Qt::Unchecked; } }; auto setData = [this](const Domain::Task::Ptr &task, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) { return false; } const auto currentTitle = task->title(); if (role == Qt::EditRole) task->setTitle(value.toString()); else task->setDone(value.toInt() == Qt::Checked); const auto job = m_taskRepository->update(task); installHandler(job, i18n("Cannot modify task %1 in Inbox", currentTitle)); return true; }; auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const Domain::Task::Ptr &task) { auto parentTask = task.objectCast(); if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedArtifacts = mimeData->property("objects").value(); if (droppedArtifacts.isEmpty()) return false; if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), [](const Domain::Artifact::Ptr &droppedArtifact) { return !droppedArtifact.objectCast(); })) { return false; } foreach(const auto &droppedArtifact, droppedArtifacts) { auto childTask = droppedArtifact.objectCast(); if (parentTask) { const auto job = m_taskRepository->associate(parentTask, childTask); installHandler(job, i18n("Cannot move task %1 as sub-task of %2", childTask->title(), parentTask->title())); } else { const auto job = m_taskRepository->dissociate(childTask); installHandler(job, i18n("Cannot deparent task %1 from its parent", childTask->title())); } } return true; }; auto drag = [](const Domain::Task::List &tasks) -> QMimeData* { if (tasks.isEmpty()) return Q_NULLPTR; auto draggedArtifacts = Domain::Artifact::List(); draggedArtifacts.reserve(tasks.size()); foreach (const Domain::Task::Ptr &task, tasks) { draggedArtifacts.append(task.objectCast()); } auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(draggedArtifacts)); return data; }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); } diff --git a/src/presentation/workdaypagemodel.cpp b/src/presentation/workdaypagemodel.cpp index 988d4c7d..71ad3e60 100644 --- a/src/presentation/workdaypagemodel.cpp +++ b/src/presentation/workdaypagemodel.cpp @@ -1,205 +1,215 @@ /* This file is part of Zanshin Copyright 2015 Theo Vaucher 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "workdaypagemodel.h" #include #include #include "domain/noterepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/querytreemodel.h" #include "utils/datetime.h" using namespace Presentation; WorkdayPageModel::WorkdayPageModel(const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : PageModel(parent), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } Domain::Artifact::Ptr WorkdayPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { const auto parentData = parentIndex.data(QueryTreeModelBase::ObjectRole); const auto parentArtifact = parentData.value(); const auto parentTask = parentArtifact.objectCast(); auto task = Domain::Task::Ptr::create(); task->setTitle(title); if (!parentTask) task->setStartDate(Utils::DateTime::currentDate()); const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) : m_taskRepository->create(task); installHandler(job, i18n("Cannot add task %1 in Workday", title)); return task; } void WorkdayPageModel::removeItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); if (task) { const auto job = m_taskRepository->remove(task); installHandler(job, i18n("Cannot remove task %1 from Workday", task->title())); } } void WorkdayPageModel::promoteItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); Q_ASSERT(task); const auto job = m_taskRepository->promoteToProject(task); installHandler(job, i18n("Cannot promote task %1 to be a project", task->title())); } QAbstractItemModel *WorkdayPageModel::createCentralListModel() { auto query = [this](const Domain::Artifact::Ptr &artifact) -> Domain::QueryResultInterface::Ptr { if (!artifact) return Domain::QueryResult::copy(m_taskQueries->findWorkdayTopLevel()); else if (auto task = artifact.dynamicCast()) return Domain::QueryResult::copy(m_taskQueries->findChildren(task)); else return Domain::QueryResult::Ptr(); }; auto flags = [](const Domain::Artifact::Ptr &artifact) { const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; return artifact.dynamicCast() ? (defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled) : defaultFlags; }; - auto data = [this](const Domain::Artifact::Ptr &artifact, int role) -> QVariant { + using AdditionalInfo = Domain::QueryResult::Ptr; // later on we'll want a struct with the context query as well + + auto data = [](const Domain::Artifact::Ptr &artifact, int role, const AdditionalInfo &projectQueryResult) -> QVariant { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return artifact->title(); case Qt::CheckStateRole: if (auto task = artifact.dynamicCast()) { return task->isDone() ? Qt::Checked : Qt::Unchecked; } break; - case ProjectRole: - case Qt::ToolTipRole: - if (auto task = artifact.dynamicCast()) { - static Domain::QueryResult::Ptr lastProjectResult; - auto projectResult = m_taskQueries->findProject(task); - if (projectResult) { - // keep a refcount to it, for next time we get here... - lastProjectResult = projectResult; - if (!projectResult->data().isEmpty()) { - Domain::Project::Ptr project = projectResult->data().at(0); - return i18n("Project: %1", project->name()); - } - } - return i18n("Inbox"); + case Presentation::QueryTreeModelBase::AdditionalInfoRole: + if (projectQueryResult && !projectQueryResult->data().isEmpty()) { + Domain::Project::Ptr project = projectQueryResult->data().at(0); + return i18n("Project: %1", project->name()); } - break; + return i18n("Inbox"); // TODO add source name default: break; } return QVariant(); }; + auto fetchAdditionalInfo = [this](const QModelIndex &index, const Domain::Artifact::Ptr &artifact) -> AdditionalInfo { + if (index.parent().isValid()) // children are in the same collection as their parent, so the same project + return nullptr; + if (auto task = artifact.dynamicCast()) { + AdditionalInfo projectQueryResult = m_taskQueries->findProject(task); + if (projectQueryResult) { + QPersistentModelIndex persistentIndex(index); + projectQueryResult->addPostInsertHandler([persistentIndex](const Domain::Project::Ptr &, int) { + // When a project was found (inserted into the result), update the rendering of the item + auto model = const_cast(persistentIndex.model()); + model->dataChanged(persistentIndex, persistentIndex); + }); + } + return projectQueryResult; + } + return nullptr; + }; + auto setData = [this](const Domain::Artifact::Ptr &artifact, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) { return false; } if (auto task = artifact.dynamicCast()) { const auto currentTitle = task->title(); if (role == Qt::EditRole) task->setTitle(value.toString()); else task->setDone(value.toInt() == Qt::Checked); const auto job = m_taskRepository->update(task); installHandler(job, i18n("Cannot modify task %1 in Workday", currentTitle)); return true; } return false; }; auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const Domain::Artifact::Ptr &artifact) { auto parentTask = artifact.objectCast(); if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedArtifacts = mimeData->property("objects").value(); if (droppedArtifacts.isEmpty()) return false; if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), [](const Domain::Artifact::Ptr &droppedArtifact) { return !droppedArtifact.objectCast(); })) { return false; } foreach(const auto &droppedArtifact, droppedArtifacts) { auto childTask = droppedArtifact.objectCast(); if (parentTask) { const auto job = m_taskRepository->associate(parentTask, childTask); installHandler(job, i18n("Cannot move task %1 as sub-task of %2", childTask->title(), parentTask->title())); } else { childTask->setStartDate(Utils::DateTime::currentDate()); auto job = m_taskRepository->dissociate(childTask); installHandler(job, i18n("Cannot deparent task %1 from its parent", childTask->title())); } } return true; }; auto drag = [](const Domain::Artifact::List &artifacts) -> QMimeData* { if (artifacts.isEmpty()) return Q_NULLPTR; auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(artifacts)); return data; }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, fetchAdditionalInfo, this); } diff --git a/src/presentation/workdaypagemodel.h b/src/presentation/workdaypagemodel.h index f1c0abb2..a4cf340d 100644 --- a/src/presentation/workdaypagemodel.h +++ b/src/presentation/workdaypagemodel.h @@ -1,57 +1,56 @@ /* This file is part of Zanshin Copyright 2015 Theo Vaucher 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef PRESENTATION_WORKDAYPAGEMODEL_H #define PRESENTATION_WORKDAYPAGEMODEL_H #include "presentation/pagemodel.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" namespace Presentation { class WorkdayPageModel : public PageModel { Q_OBJECT public: - enum { ProjectRole = 0x386F4B }; explicit WorkdayPageModel(const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent = Q_NULLPTR); Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) Q_DECL_OVERRIDE; void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; void promoteItem(const QModelIndex &index) Q_DECL_OVERRIDE; private: QAbstractItemModel *createCentralListModel() Q_DECL_OVERRIDE; Domain::TaskQueries::Ptr m_taskQueries; Domain::TaskRepository::Ptr m_taskRepository; }; } #endif // PRESENTATION_WORKDAYPAGEMODEL_H diff --git a/src/widgets/itemdelegate.cpp b/src/widgets/itemdelegate.cpp index d8d38614..082d4435 100644 --- a/src/widgets/itemdelegate.cpp +++ b/src/widgets/itemdelegate.cpp @@ -1,153 +1,174 @@ /* This file is part of Zanshin Copyright 2014-2016 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include "itemdelegate.h" #include #include #include #include #include "domain/note.h" #include "domain/task.h" +#include "presentation/pagemodel.h" #include "presentation/querytreemodelbase.h" #include "utils/datetime.h" using namespace Widgets; ItemDelegate::ItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QSize ItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - // Make sure they all get the height needed for a check indicator QStyleOptionViewItem opt = option; initStyleOption(&opt, index); + // Make sure they all get the height needed for a check indicator opt.features = QStyleOptionViewItem::HasCheckIndicator; + // and for a date on the right opt.text += ' ' + QLocale().dateFormat(QLocale::ShortFormat).toUpper() + ' '; - return QStyledItemDelegate::sizeHint(opt, index); + QSize sz = QStyledItemDelegate::sizeHint(opt, index); + const auto additionalInfo = index.data(Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(); + if (!additionalInfo.isEmpty()) + sz.rheight() += opt.fontMetrics.height(); + return sz; } void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { const auto data = index.data(Presentation::QueryTreeModelBase::ObjectRole); auto task = Domain::Task::Ptr(); auto artifact = data.value(); if (artifact) { task = artifact.dynamicCast(); } else { task = data.value(); auto note = data.value(); artifact = task ? task.staticCast() : note.staticCast(); } auto opt = QStyleOptionViewItem(option); initStyleOption(&opt, index); const auto widget = opt.widget; const auto style = widget ? widget->style() : QApplication::style(); const auto isDone = task ? task->isDone() : false; const auto isEnabled = (opt.state & QStyle::State_Enabled); const auto isActive = (opt.state & QStyle::State_Active); const auto isSelected = (opt.state & QStyle::State_Selected); const auto isEditing = (opt.state & QStyle::State_Editing); const auto startDate = task ? task->startDate() : QDate(); const auto dueDate = task ? task->dueDate() : QDate(); + const auto additionalInfo = index.data(Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(); const auto currentDate = Utils::DateTime::currentDate(); const auto onStartDate = startDate.isValid() && startDate <= currentDate; const auto pastDueDate = dueDate.isValid() && dueDate < currentDate; const auto onDueDate = dueDate.isValid() && dueDate == currentDate; const auto taskDelegate = task ? task->delegate() : Domain::Task::Delegate(); const auto baseFont = opt.font; const auto summaryFont = [=] { auto font = baseFont; font.setStrikeOut(isDone); font.setBold(!isDone && (onStartDate || onDueDate || pastDueDate)); font.setItalic(taskDelegate.isValid()); return font; }(); const auto summaryMetrics = QFontMetrics(summaryFont); const auto colorGroup = (isEnabled && !isActive) ? QPalette::Inactive : isEnabled ? QPalette::Normal : QPalette::Disabled; const auto colorRole = (isSelected && !isEditing) ? QPalette::HighlightedText : QPalette::Text; const auto baseColor = opt.palette.color(colorGroup, colorRole); const auto summaryColor = isDone ? baseColor : pastDueDate ? QColor(Qt::red) : onDueDate ? QColor("orange") : baseColor; const auto summaryText = taskDelegate.isValid() ? i18n("(%1) %2", taskDelegate.display(), opt.text) : opt.text; const auto dueDateText = dueDate.isValid() ? QLocale().toString(dueDate, QLocale::ShortFormat) : QString(); const auto textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1; const auto dueDateWidth = dueDate.isValid() ? (summaryMetrics.width(dueDateText) + 2 * textMargin) : 0; const auto checkRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, widget); - const auto summaryRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget) - .adjusted(textMargin, 0, -dueDateWidth - textMargin, 0); - const auto dueDateRect = opt.rect.adjusted(opt.rect.width() - dueDateWidth, 0, 0, 0); + const auto textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget) + .adjusted(textMargin, 0, - textMargin, 0); + auto summaryRect = textRect.adjusted(0, 0, -dueDateWidth, 0); + if (!additionalInfo.isEmpty()) + summaryRect.setHeight(summaryRect.height() - opt.fontMetrics.height()); + auto dueDateRect = textRect.adjusted(textRect.width() - dueDateWidth, 0, 0, 0); + dueDateRect.setHeight(summaryRect.height()); + const auto additionalInfoRect = QRect(textRect.x(), summaryRect.bottom(), textRect.width(), textRect.height() - summaryRect.height()); // Draw background style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget); // Draw the check box if (task) { auto checkOption = opt; checkOption.rect = checkRect; checkOption.state = option.state & ~QStyle::State_HasFocus; checkOption.state |= isDone ? QStyle::State_On : QStyle::State_Off; style->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &checkOption, painter, widget); } // Draw the summary if (!summaryText.isEmpty()) { painter->setPen(summaryColor); painter->setFont(summaryFont); painter->drawText(summaryRect, Qt::AlignVCenter, summaryMetrics.elidedText(summaryText, Qt::ElideRight, summaryRect.width())); } // Draw the due date if (!dueDateText.isEmpty()) { painter->drawText(dueDateRect, Qt::AlignCenter, dueDateText); } + + // Draw the second line + if (!additionalInfo.isEmpty()) { + QFont additionalInfoFont = baseFont; + additionalInfoFont.setItalic(true); + additionalInfoFont.setPointSize(additionalInfoFont.pointSize() - 1); + painter->setFont(additionalInfoFont); + painter->drawText(additionalInfoRect, Qt::AlignLeft, additionalInfo); + } } diff --git a/tests/manual/tasktreeviewer.cpp b/tests/manual/tasktreeviewer.cpp index 6abe27f3..9d89542e 100644 --- a/tests/manual/tasktreeviewer.cpp +++ b/tests/manual/tasktreeviewer.cpp @@ -1,106 +1,106 @@ /* This file is part of Zanshin Copyright 2014 Mario Bensi Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include #include #include #include #include #include "zanshin/app/dependencies.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/querytreemodel.h" #include "utils/dependencymanager.h" int main(int argc, char **argv) { QApplication app(argc, argv); // PORTING SCRIPT: move this to before the KAboutData initialization App::initializeDependencies(); KAboutData aboutData(QStringLiteral("tasktreeviewer"), QStringLiteral("Show all the tasks in tree"), QStringLiteral("1.0")); QCommandLineParser parser; KAboutData::setApplicationData(aboutData); parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); auto repository = Utils::DependencyManager::globalInstance().create(); auto queries = Utils::DependencyManager::globalInstance().create(); auto treeQuery = [&](const Domain::Task::Ptr &task) { if (!task) return queries->findTopLevel(); else return queries->findChildren(task); }; auto treeFlags = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; }; - auto treeData = [](const Domain::Task::Ptr &task, int role) -> QVariant { + auto treeData = [](const Domain::Task::Ptr &task, int role, int) -> QVariant { if (role != Qt::DisplayRole && role != Qt::CheckStateRole) { return QVariant(); } if (role == Qt::DisplayRole) return task->title(); else return task->isDone() ? Qt::Checked : Qt::Unchecked; }; auto treeSetData = [&](const Domain::Task::Ptr &task, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) { return false; } if (role == Qt::EditRole) { task->setTitle(value.toString()); } else { task->setDone(value.toInt() == Qt::Checked); } repository->update(task); return true; }; QTreeView view; view.setModel(new Presentation::QueryTreeModel(treeQuery, treeFlags, treeData, treeSetData, &view)); view.resize(640, 480); view.show(); return app.exec(); } diff --git a/tests/units/presentation/querytreemodeltest.cpp b/tests/units/presentation/querytreemodeltest.cpp index 28051013..2ca76956 100644 --- a/tests/units/presentation/querytreemodeltest.cpp +++ b/tests/units/presentation/querytreemodeltest.cpp @@ -1,985 +1,928 @@ /* This file is part of Zanshin Copyright 2014 Mario Bensi Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include #include #include #include #include "utils/mockobject.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/querytreemodel.h" #include "testlib/modeltest.h" using namespace mockitopp; Q_DECLARE_METATYPE(QModelIndex) Q_DECLARE_METATYPE(QList) class QueryTreeModelTest : public QObject { Q_OBJECT public: explicit QueryTreeModelTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qRegisterMetaType(); } private: Domain::Task::List createTasks() const { Domain::Task::List result; const QStringList titles = {"first", "second", "third"}; const QList doneStates = {true, false, false}; Q_ASSERT(titles.size() == doneStates.size()); result.reserve(titles.size()); for (int i = 0; i < titles.size(); i++) { auto task = Domain::Task::Ptr::create(); task->setTitle(titles.at(i)); task->setDone(doneStates.at(i)); result << task; } return result; } Domain::Task::List createChildrenTasks() const { Domain::Task::List result; const QStringList titles = {"childFirst", "childSecond", "childThird"}; const QList doneStates = {true, false, false}; Q_ASSERT(titles.size() == doneStates.size()); result.reserve(titles.size()); for (int i = 0; i < titles.size(); i++) { auto task = Domain::Task::Ptr::create(); task->setTitle(titles.at(i)); task->setDone(doneStates.at(i)); result << task; } return result; } + static QVariant standardDataFunction(const Domain::Task::Ptr &task, int role, int) + { + if (role != Qt::DisplayRole && role != Qt::CheckStateRole) { + return QVariant(); + } + + if (role == Qt::DisplayRole) + return task->title(); + else + return task->isDone() ? Qt::Checked : Qt::Unchecked; + } + private slots: void shouldHaveRoleNames() { // GIVEN auto queryGenerator = [](const QColor &) { return Domain::QueryResult::Ptr(); }; auto flagsFunction = [](const QColor &) { return Qt::NoItemFlags; }; - auto dataFunction = [](const QColor &, int) { + auto dataFunction = [](const QColor &, int, int) { return QVariant(); }; auto setDataFunction = [](const QColor &, const QVariant &, int) { return false; }; Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction); // WHEN auto roles = model.roleNames(); // THEN QCOMPARE(roles.value(Qt::DisplayRole), QByteArray("display")); QCOMPARE(roles.value(Presentation::QueryTreeModelBase::ObjectRole), QByteArray("object")); QCOMPARE(roles.value(Presentation::QueryTreeModelBase::IconNameRole), QByteArray("icon")); QCOMPARE(roles.value(Presentation::QueryTreeModelBase::IsDefaultRole), QByteArray("default")); } void shouldListTasks() { // GIVEN auto tasks = createTasks(); auto provider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, tasks) provider->append(task); auto childrenTasks = createChildrenTasks(); auto childrenProvider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, childrenTasks) childrenProvider->append(task); auto childrenList = Domain::QueryResult::create(childrenProvider); auto emptyProvider = Domain::QueryResultProvider::Ptr::create(); auto emptyList = Domain::QueryResult::create(emptyProvider); Utils::MockObject queryMock; queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList); // WHEN auto queryGenerator = [&](const Domain::Task::Ptr &task) { if (!task) return Domain::QueryResult::create(provider); else return queryMock.getInstance()->findChildren(task); }; auto flagsFunction = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; }; - auto dataFunction = [](const Domain::Task::Ptr &task, int role) -> QVariant { - if (role != Qt::DisplayRole && role != Qt::CheckStateRole) { - return QVariant(); - } - - if (role == Qt::DisplayRole) - return task->title(); - else - return task->isDone() ? Qt::Checked : Qt::Unchecked; - }; auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) { return false; }; - Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, Q_NULLPTR); + Presentation::QueryTreeModel model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, Q_NULLPTR); new ModelTest(&model, this); // THEN QCOMPARE(model.rowCount(), 3); QCOMPARE(model.rowCount(model.index(0, 0)), 3); QCOMPARE(model.rowCount(model.index(1, 0)), 0); QCOMPARE(model.rowCount(model.index(2, 0)), 0); QCOMPARE(model.rowCount(model.index(0, 0, model.index(0, 0))), 0); QCOMPARE(model.rowCount(model.index(1, 0, model.index(0, 0))), 0); QCOMPARE(model.rowCount(model.index(2, 0, model.index(0, 0))), 0); QCOMPARE(model.rowCount(model.index(3, 0, model.index(0, 0))), 3); for (int i = 0; i < tasks.size(); i++) { auto task = tasks.at(i); auto index = model.index(i, 0); QCOMPARE(model.data(index), model.data(index, Qt::DisplayRole)); QCOMPARE(model.data(index).toString(), task->title()); QCOMPARE(model.data(index, Qt::CheckStateRole).toInt() == Qt::Checked, task->isDone()); QCOMPARE(model.data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked, !task->isDone()); } for (int i = 0; i < childrenTasks.size(); i++) { auto task = childrenTasks.at(i); auto index = model.index(i, 0, model.index(0, 0)); QCOMPARE(model.data(index), model.data(index, Qt::DisplayRole)); QCOMPARE(model.data(index).toString(), task->title()); QCOMPARE(model.data(index, Qt::CheckStateRole).toInt() == Qt::Checked, task->isDone()); QCOMPARE(model.data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked, !task->isDone()); } } void shouldDealWithNullQueriesProperly() { // GIVEN auto queryGenerator = [](const QString &) { return Domain::QueryResult::Ptr(); }; auto flagsFunction = [](const QString &) { return Qt::NoItemFlags; }; - auto dataFunction = [](const QString &, int) { + auto dataFunction = [](const QString &, int, int) { return QVariant(); }; auto setDataFunction = [](const QString &, const QVariant &, int) { return false; }; // WHEN Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, Q_NULLPTR); new ModelTest(&model, this); // THEN QCOMPARE(model.rowCount(), 0); } void shouldReactToTaskAdd() { // GIVEN auto tasks = createTasks(); auto provider = Domain::QueryResultProvider::Ptr::create(); provider->append(tasks.at(1)); provider->append(tasks.at(2)); auto childrenTasks = createChildrenTasks(); auto childrenProvider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, childrenTasks) childrenProvider->append(task); auto childrenList = Domain::QueryResult::create(childrenProvider); auto emptyProvider = Domain::QueryResultProvider::Ptr::create(); auto emptyList = Domain::QueryResult::create(emptyProvider); Utils::MockObject queryMock; queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList); auto queryGenerator = [&](const Domain::Task::Ptr &task) { if (!task) return Domain::QueryResult::create(provider); else return queryMock.getInstance()->findChildren(task); }; auto flagsFunction = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; }; - auto dataFunction = [](const Domain::Task::Ptr &task, int role) -> QVariant { - if (role != Qt::DisplayRole && role != Qt::CheckStateRole) { - return QVariant(); - } - if (role == Qt::DisplayRole) - return task->title(); - else - return task->isDone() ? Qt::Checked : Qt::Unchecked; - }; auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) { return false; }; - Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, Q_NULLPTR); + Presentation::QueryTreeModel model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, Q_NULLPTR); new ModelTest(&model, this); QSignalSpy aboutToBeInsertedSpy(&model, &QAbstractItemModel::rowsAboutToBeInserted); QSignalSpy insertedSpy(&model, &QAbstractItemModel::rowsInserted); // WHEN queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList); provider->insert(0, tasks.at(0)); // THEN QCOMPARE(aboutToBeInsertedSpy.size(), 1); QCOMPARE(aboutToBeInsertedSpy.first().at(0).toModelIndex(), QModelIndex()); QCOMPARE(aboutToBeInsertedSpy.first().at(1).toInt(), 0); QCOMPARE(aboutToBeInsertedSpy.first().at(2).toInt(), 0); QCOMPARE(insertedSpy.size(), 1); QCOMPARE(insertedSpy.first().at(0).toModelIndex(), QModelIndex()); QCOMPARE(insertedSpy.first().at(1).toInt(), 0); QCOMPARE(insertedSpy.first().at(2).toInt(), 0); } void shouldReactToChilrenTaskAdd() { // GIVEN auto tasks = createTasks(); auto provider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, tasks) provider->append(task); auto childrenTasks = createChildrenTasks(); auto childrenProvider = Domain::QueryResultProvider::Ptr::create(); childrenProvider->append(childrenTasks.at(0)); childrenProvider->append(childrenTasks.at(1)); auto childrenList = Domain::QueryResult::create(childrenProvider); auto emptyProvider = Domain::QueryResultProvider::Ptr::create(); auto emptyList = Domain::QueryResult::create(emptyProvider); Utils::MockObject queryMock; queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList); auto queryGenerator = [&](const Domain::Task::Ptr &task) { if (!task) return Domain::QueryResult::create(provider); else return queryMock.getInstance()->findChildren(task); }; auto flagsFunction = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; }; - auto dataFunction = [](const Domain::Task::Ptr &task, int role) -> QVariant { - if (role != Qt::DisplayRole && role != Qt::CheckStateRole) { - return QVariant(); - } - - if (role == Qt::DisplayRole) - return task->title(); - else - return task->isDone() ? Qt::Checked : Qt::Unchecked; - }; auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) { return false; }; - Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, Q_NULLPTR); + Presentation::QueryTreeModel model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, Q_NULLPTR); new ModelTest(&model, this); QSignalSpy aboutToBeInsertedSpy(&model, &QAbstractItemModel::rowsAboutToBeInserted); QSignalSpy insertedSpy(&model, &QAbstractItemModel::rowsInserted); // WHEN queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList); childrenProvider->insert(1, tasks.at(2)); // THEN QCOMPARE(aboutToBeInsertedSpy.size(), 1); QCOMPARE(aboutToBeInsertedSpy.first().at(0).toModelIndex(), model.index(0, 0)); QCOMPARE(aboutToBeInsertedSpy.first().at(1).toInt(), 1); QCOMPARE(aboutToBeInsertedSpy.first().at(2).toInt(), 1); QCOMPARE(insertedSpy.size(), 1); QCOMPARE(insertedSpy.first().at(0).toModelIndex(), model.index(0, 0)); QCOMPARE(insertedSpy.first().at(1).toInt(), 1); QCOMPARE(insertedSpy.first().at(2).toInt(), 1); } void shouldReactToTaskRemove() { // GIVEN auto tasks = createTasks(); auto provider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, tasks) provider->append(task); auto childrenTasks = createChildrenTasks(); auto childrenProvider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, childrenTasks) childrenProvider->append(task); auto childrenList = Domain::QueryResult::create(childrenProvider); auto emptyProvider = Domain::QueryResultProvider::Ptr::create(); auto emptyList = Domain::QueryResult::create(emptyProvider); Utils::MockObject queryMock; queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList); auto queryGenerator = [&](const Domain::Task::Ptr &task) { if (!task) return Domain::QueryResult::create(provider); else return queryMock.getInstance()->findChildren(task); }; auto flagsFunction = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; }; - auto dataFunction = [](const Domain::Task::Ptr &task, int role) -> QVariant { - if (role != Qt::DisplayRole && role != Qt::CheckStateRole) { - return QVariant(); - } - - if (role == Qt::DisplayRole) - return task->title(); - else - return task->isDone() ? Qt::Checked : Qt::Unchecked; - }; auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) { return false; }; - Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, Q_NULLPTR); + Presentation::QueryTreeModel model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, Q_NULLPTR); new ModelTest(&model, this); QSignalSpy aboutToBeRemovedSpy(&model, &QAbstractItemModel::rowsAboutToBeRemoved); QSignalSpy removedSpy(&model, &QAbstractItemModel::rowsRemoved); QSignalSpy aboutToBeInsertedSpy(&model, &QAbstractItemModel::rowsAboutToBeInserted); QSignalSpy insertedSpy(&model, &QAbstractItemModel::rowsInserted); QModelIndex removeIndex = model.index(0, 0); // WHEN // Remove children childrenProvider->removeAt(0); childrenProvider->removeAt(0); childrenProvider->removeAt(0); // Move children to Top Level provider->append(childrenTasks.at(0)); provider->append(childrenTasks.at(1)); provider->append(childrenTasks.at(2)); // Remove firt element from topLevel provider->removeAt(0); // THEN QCOMPARE(aboutToBeRemovedSpy.size(), 4); QCOMPARE(removedSpy.size(), 4); for (int i = 0; i < aboutToBeRemovedSpy.size(); i++) { if (i != 3) QCOMPARE(aboutToBeRemovedSpy.at(i).at(0).toModelIndex(), removeIndex); else QCOMPARE(aboutToBeRemovedSpy.at(i).at(0).toModelIndex(), QModelIndex()); QCOMPARE(aboutToBeRemovedSpy.at(i).at(1).toInt(), 0); QCOMPARE(aboutToBeRemovedSpy.at(i).at(2).toInt(), 0); if (i != 3) QCOMPARE(removedSpy.at(i).at(0).toModelIndex(), removeIndex); else QCOMPARE(removedSpy.at(i).at(0).toModelIndex(), QModelIndex()); QCOMPARE(removedSpy.at(i).at(1).toInt(), 0); QCOMPARE(removedSpy.at(i).at(2).toInt(), 0); } QCOMPARE(aboutToBeInsertedSpy.size(), 3); QCOMPARE(insertedSpy.size(), 3); for (int i = 0; i < aboutToBeInsertedSpy.size(); i++) { QCOMPARE(aboutToBeInsertedSpy.at(i).at(0).toModelIndex(), QModelIndex()); QCOMPARE(aboutToBeInsertedSpy.at(i).at(1).toInt(), i + 3); QCOMPARE(aboutToBeInsertedSpy.at(i).at(2).toInt(), i + 3); QCOMPARE(insertedSpy.at(i).at(0).toModelIndex(), QModelIndex()); QCOMPARE(insertedSpy.at(i).at(1).toInt(), i + 3); QCOMPARE(insertedSpy.at(i).at(1).toInt(), i + 3); } } void shouldReactToTaskChange() { // GIVEN // GIVEN auto tasks = createTasks(); auto provider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, tasks) provider->append(task); auto childrenTasks = createChildrenTasks(); auto childrenProvider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, childrenTasks) childrenProvider->append(task); auto childrenList = Domain::QueryResult::create(childrenProvider); auto emptyProvider = Domain::QueryResultProvider::Ptr::create(); auto emptyList = Domain::QueryResult::create(emptyProvider); Utils::MockObject queryMock; queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList); // WHEN auto queryGenerator = [&](const Domain::Task::Ptr &task) { if (!task) return Domain::QueryResult::create(provider); else return queryMock.getInstance()->findChildren(task); }; auto flagsFunction = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; }; - auto dataFunction = [](const Domain::Task::Ptr &task, int role) -> QVariant { - if (role != Qt::DisplayRole && role != Qt::CheckStateRole) { - return QVariant(); - } - - if (role == Qt::DisplayRole) - return task->title(); - else - return task->isDone() ? Qt::Checked : Qt::Unchecked; - }; auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) { return false; }; - Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, Q_NULLPTR); + Presentation::QueryTreeModel model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, Q_NULLPTR); new ModelTest(&model, this); QSignalSpy dataChangedSpy(&model, &QAbstractItemModel::dataChanged); // WHEN tasks.at(2)->setDone(true); childrenTasks.at(2)->setDone(true); provider->replace(2, tasks.at(2)); childrenProvider->replace(2, tasks.at(2)); // THEN QCOMPARE(dataChangedSpy.size(), 2); QCOMPARE(dataChangedSpy.first().at(0).toModelIndex(), model.index(2, 0)); QCOMPARE(dataChangedSpy.first().at(1).toModelIndex(), model.index(2, 0)); QCOMPARE(dataChangedSpy.last().at(0).toModelIndex(), model.index(2, 0, model.index(0, 0))); QCOMPARE(dataChangedSpy.last().at(1).toModelIndex(), model.index(2, 0, model.index(0, 0))); } void shouldAllowEditsAndChecks() { // GIVEN auto tasks = createTasks(); auto provider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, tasks) provider->append(task); auto childrenTasks = createChildrenTasks(); auto childrenProvider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, childrenTasks) childrenProvider->append(task); auto childrenList = Domain::QueryResult::create(childrenProvider); auto emptyProvider = Domain::QueryResultProvider::Ptr::create(); auto emptyList = Domain::QueryResult::create(emptyProvider); Utils::MockObject queryMock; queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList); // WHEN auto queryGenerator = [&](const Domain::Task::Ptr &task) { if (!task) return Domain::QueryResult::create(provider); else return queryMock.getInstance()->findChildren(task); }; auto flagsFunction = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; }; - auto dataFunction = [](const Domain::Task::Ptr &task, int role) -> QVariant { - if (role != Qt::DisplayRole && role != Qt::CheckStateRole) { - return QVariant(); - } - - if (role == Qt::DisplayRole) - return task->title(); - else - return task->isDone() ? Qt::Checked : Qt::Unchecked; - }; auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) { return false; }; - Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, Q_NULLPTR); + Presentation::QueryTreeModel model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, Q_NULLPTR); new ModelTest(&model, this); // WHEN // Nothing particular // THEN for (int row = 0; row < tasks.size(); row++) { QVERIFY(model.flags(model.index(row, 0)) & Qt::ItemIsEditable); QVERIFY(model.flags(model.index(row, 0)) & Qt::ItemIsUserCheckable); } for (int row = 0; row < childrenTasks.size(); row++) { QVERIFY(model.flags(model.index(row, 0, model.index(0, 0))) & Qt::ItemIsEditable); QVERIFY(model.flags(model.index(row, 0, model.index(0, 0))) & Qt::ItemIsUserCheckable); } } void shouldSaveChanges() { // GIVEN auto tasks = createTasks(); const int taskPos = 1; const auto task = tasks[taskPos]; auto provider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, tasks) provider->append(task); auto childrenTasks = createChildrenTasks(); auto childrenProvider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, childrenTasks) childrenProvider->append(task); auto childrenList = Domain::QueryResult::create(childrenProvider); auto emptyProvider = Domain::QueryResultProvider::Ptr::create(); auto emptyList = Domain::QueryResult::create(emptyProvider); Utils::MockObject queryMock; queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList); queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList); Utils::MockObject repositoryMock; repositoryMock(&Domain::TaskRepository::update).when(task).thenReturn(Q_NULLPTR); auto queryGenerator = [&](const Domain::Task::Ptr &task) { if (!task) return Domain::QueryResult::create(provider); else return queryMock.getInstance()->findChildren(task); }; auto flagsFunction = [](const Domain::Task::Ptr &) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; }; - auto dataFunction = [](const Domain::Task::Ptr &task, int role) -> QVariant { - if (role != Qt::DisplayRole && role != Qt::CheckStateRole) { - return QVariant(); - } - - if (role == Qt::DisplayRole) - return task->title(); - else - return task->isDone() ? Qt::Checked : Qt::Unchecked; - }; auto setDataFunction = [&](const Domain::Task::Ptr &task, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) { return false; } if (role == Qt::EditRole) { task->setTitle(value.toString()); } else { task->setDone(value.toInt() == Qt::Checked); } repositoryMock.getInstance()->update(task); return true; }; - Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, Q_NULLPTR); + Presentation::QueryTreeModel model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, Q_NULLPTR); new ModelTest(&model, this); QSignalSpy titleChangedSpy(task.data(), &Domain::Task::titleChanged); QSignalSpy doneChangedSpy(task.data(), &Domain::Task::doneChanged); // WHEN const auto index = model.index(taskPos, 0); model.setData(index, "alternate second"); model.setData(index, Qt::Checked, Qt::CheckStateRole); // THEN QVERIFY(repositoryMock(&Domain::TaskRepository::update).when(task).exactly(2)); QCOMPARE(titleChangedSpy.size(), 1); QCOMPARE(titleChangedSpy.first().first().toString(), QStringLiteral("alternate second")); QCOMPARE(doneChangedSpy.size(), 1); QCOMPARE(doneChangedSpy.first().first().toBool(), true); } void shouldProvideUnderlyingObject() { // GIVEN auto provider = Domain::QueryResultProvider::Ptr::create(); provider->append(Qt::red); provider->append(Qt::green); provider->append(Qt::blue); auto queryGenerator = [&](const QColor &color) { if (!color.isValid()) return Domain::QueryResult::create(provider); else return Domain::QueryResult::Ptr(); }; auto flagsFunction = [](const QColor &) { return Qt::NoItemFlags; }; - auto dataFunction = [](const QColor &, int) { + auto dataFunction = [](const QColor &, int, int) { return QVariant(); }; auto setDataFunction = [](const QColor &, const QVariant &, int) { return false; }; Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction); new ModelTest(&model, this); // WHEN const QModelIndex index = model.index(1, 0); const QVariant data = index.data(Presentation::QueryTreeModelBase::ObjectRole); // THEN QVERIFY(data.isValid()); QCOMPARE(data.value(), provider->data().at(1)); } void shouldProvideUnderlyingTaskAsArtifact() { // GIVEN auto provider = Domain::QueryResultProvider::Ptr::create(); foreach (const auto &task, createTasks()) provider->append(task); auto queryGenerator = [&](const Domain::Task::Ptr &artifact) { if (!artifact) return Domain::QueryResult::create(provider); else return Domain::QueryResult::Ptr(); }; auto flagsFunction = [](const Domain::Task::Ptr &) { return Qt::NoItemFlags; }; - auto dataFunction = [](const Domain::Task::Ptr &, int) { + auto dataFunction = [](const Domain::Task::Ptr &, int, int) { return QVariant(); }; auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) { return false; }; Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction); new ModelTest(&model, this); // WHEN const QModelIndex index = model.index(1, 0); const QVariant data = index.data(Presentation::QueryTreeModelBase::ObjectRole); // THEN QVERIFY(data.isValid()); // Note it says Artifact and *not* Task here, it should up-cast automatically QVERIFY(!data.value().isNull()); QCOMPARE(data.value(), provider->data().at(1).staticCast()); } void shouldMoveOnlyDuringDragAndDrop() { // GIVEN auto queryGenerator = [&] (const QColor &) { return Domain::QueryResult::Ptr(); }; auto flagsFunction = [] (const QColor &) { return Qt::NoItemFlags; }; - auto dataFunction = [] (const QColor &, int) { + auto dataFunction = [] (const QColor &, int, int) { return QVariant(); }; auto setDataFunction = [] (const QColor &, const QVariant &, int) { return false; }; auto dropFunction = [] (const QMimeData *, Qt::DropAction, const QColor &) { return false; }; auto dragFunction = [] (const QList &) { return Q_NULLPTR; }; Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, - dropFunction, dragFunction); + dropFunction, dragFunction, nullptr); // THEN QCOMPARE(model.supportedDragActions(), Qt::MoveAction); QCOMPARE(model.supportedDropActions(), Qt::MoveAction); } void shouldCreateMimeData() { // GIVEN auto provider = Domain::QueryResultProvider::Ptr::create(); provider->append(Qt::red); provider->append(Qt::green); provider->append(Qt::blue); auto queryGenerator = [&] (const QColor &color) { if (!color.isValid()) return Domain::QueryResult::create(provider); else return Domain::QueryResult::Ptr(); }; auto flagsFunction = [] (const QColor &) { return Qt::NoItemFlags; }; - auto dataFunction = [] (const QColor &, int) { + auto dataFunction = [] (const QColor &, int, int) { return QVariant(); }; auto setDataFunction = [] (const QColor &, const QVariant &, int) { return false; }; auto dropFunction = [] (const QMimeData *, Qt::DropAction, const QColor &) { return false; }; auto dragFunction = [] (const QList &colors) { auto mimeData = new QMimeData; mimeData->setColorData(QVariant::fromValue(colors)); return mimeData; }; Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, - dropFunction, dragFunction); + dropFunction, dragFunction, nullptr); new ModelTest(&model, this); // WHEN auto data = std::unique_ptr(model.mimeData(QList() << model.index(1, 0) << model.index(2, 0))); // THEN QVERIFY(data.get()); QVERIFY(model.mimeTypes().contains(QStringLiteral("application/x-zanshin-object"))); QList colors; colors << Qt::green << Qt::blue; QCOMPARE(data->colorData().value>(), colors); } void shouldDropMimeData_data() { QTest::addColumn("row"); QTest::addColumn("column"); QTest::addColumn("parentRow"); QTest::addColumn("callExpected"); QTest::newRow("drop on object") << -1 << -1 << 2 << true; QTest::newRow("drop between object") << 1 << 0 << -1 << true; QTest::newRow("drop in empty area") << -1 << -1 << -1 << true; } void shouldDropMimeData() { // GIVEN QFETCH(int, row); QFETCH(int, column); QFETCH(int, parentRow); QFETCH(bool, callExpected); bool dropCalled = false; const QMimeData *droppedData = Q_NULLPTR; QColor colorSeen; auto provider = Domain::QueryResultProvider::Ptr::create(); provider->append(Qt::red); provider->append(Qt::green); provider->append(Qt::blue); auto queryGenerator = [&] (const QColor &color) { if (!color.isValid()) return Domain::QueryResult::create(provider); else return Domain::QueryResult::Ptr(); }; auto flagsFunction = [] (const QColor &) { return Qt::NoItemFlags; }; - auto dataFunction = [] (const QColor &, int) { + auto dataFunction = [] (const QColor &, int, int) { return QVariant(); }; auto setDataFunction = [] (const QColor &, const QVariant &, int) { return false; }; auto dropFunction = [&] (const QMimeData *data, Qt::DropAction, const QColor &color) { dropCalled = true; droppedData = data; colorSeen = color; return false; }; auto dragFunction = [] (const QList &) -> QMimeData* { return Q_NULLPTR; }; Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, - dropFunction, dragFunction); + dropFunction, dragFunction, nullptr); new ModelTest(&model, this); // WHEN auto data = std::make_unique(); const QModelIndex parent = parentRow >= 0 ? model.index(parentRow, 0) : QModelIndex(); model.dropMimeData(data.get(), Qt::MoveAction, row, column, parent); // THEN QCOMPARE(dropCalled, callExpected); if (callExpected) { QCOMPARE(droppedData, data.get()); QCOMPARE(colorSeen, parent.data(Presentation::QueryTreeModelBase::ObjectRole).value()); } } void shouldPreventCyclesByDragAndDrop() { // GIVEN bool dropCalled = false; auto topProvider = Domain::QueryResultProvider::Ptr::create(); topProvider->append(QStringLiteral("1")); topProvider->append(QStringLiteral("2")); topProvider->append(QStringLiteral("3")); auto firstLevelProvider = Domain::QueryResultProvider::Ptr::create(); firstLevelProvider->append(QStringLiteral("2.1")); firstLevelProvider->append(QStringLiteral("2.2")); firstLevelProvider->append(QStringLiteral("2.3")); auto secondLevelProvider = Domain::QueryResultProvider::Ptr::create(); secondLevelProvider->append(QStringLiteral("2.1.1")); secondLevelProvider->append(QStringLiteral("2.1.2")); secondLevelProvider->append(QStringLiteral("2.1.3")); auto queryGenerator = [&] (const QString &string) { if (string.isEmpty()) return Domain::QueryResult::create(topProvider); else if (string == QLatin1String("2")) return Domain::QueryResult::create(firstLevelProvider); else if (string == QLatin1String("2.1")) return Domain::QueryResult::create(secondLevelProvider); else return Domain::QueryResult::Ptr(); }; auto flagsFunction = [] (const QString &) { return Qt::NoItemFlags; }; - auto dataFunction = [] (const QString &, int) { + auto dataFunction = [] (const QString &, int, int) { return QVariant(); }; auto setDataFunction = [] (const QString &, const QVariant &, int) { return false; }; auto dropFunction = [&] (const QMimeData *, Qt::DropAction, const QString &) { dropCalled = true; return false; }; auto dragFunction = [] (const QStringList &strings) -> QMimeData* { auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(strings)); return data; }; Presentation::QueryTreeModel model(queryGenerator, flagsFunction, dataFunction, setDataFunction, - dropFunction, dragFunction); + dropFunction, dragFunction, nullptr); new ModelTest(&model, this); const auto indexes = QModelIndexList() << model.index(0, 0) << model.index(1, 0) << model.index(1, 0, model.index(1, 0)); // WHEN auto data = std::unique_ptr(model.mimeData(indexes)); const auto parent = model.index(1, 0, model.index(0, 0, model.index(1, 0))); model.dropMimeData(data.get(), Qt::MoveAction, -1, -1, parent); // THEN QVERIFY(!dropCalled); } }; ZANSHIN_TEST_MAIN(QueryTreeModelTest) #include "querytreemodeltest.moc" diff --git a/tests/units/presentation/workdaypagemodeltest.cpp b/tests/units/presentation/workdaypagemodeltest.cpp index bba9129e..294aa4a5 100644 --- a/tests/units/presentation/workdaypagemodeltest.cpp +++ b/tests/units/presentation/workdaypagemodeltest.cpp @@ -1,392 +1,399 @@ /* This file is part of Zanshin Copyright 2015 Theo Vaucher 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #include #include #include #include #include "utils/datetime.h" #include "utils/mockobject.h" #include "domain/noterepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/workdaypagemodel.h" +#include "presentation/querytreemodelbase.h" using namespace mockitopp; using namespace mockitopp::matcher; class WorkdayPageModelTest : public QObject { Q_OBJECT private slots: void shouldListWorkdayInCentralListModel() { // GIVEN const auto today = Utils::DateTime::currentDate(); // Three tasks auto task1 = Domain::Task::Ptr::create(); task1->setTitle(QStringLiteral("task1")); task1->setStartDate(today.addDays(-10)); task1->setDueDate(today); auto task2 = Domain::Task::Ptr::create(); task2->setTitle(QStringLiteral("task2")); task2->setStartDate(today); task2->setDueDate(today.addDays(10)); auto task3 = Domain::Task::Ptr::create(); task3->setTitle(QStringLiteral("task3")); task3->setStartDate(today.addYears(-4)); task3->setDueDate(today.addYears(-3)); auto taskProvider = Domain::QueryResultProvider::Ptr::create(); auto taskResult = Domain::QueryResult::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); taskProvider->append(task3); // Two tasks under the task1 auto childTask11 = Domain::Task::Ptr::create(); childTask11->setTitle(QStringLiteral("childTask11")); auto childTask12 = Domain::Task::Ptr::create(); childTask12->setTitle(QStringLiteral("childTask12")); childTask12->setStartDate(today); childTask12->setDueDate(today); auto childTaskProvider = Domain::QueryResultProvider::Ptr::create(); auto childTaskResult = Domain::QueryResult::create(childTaskProvider); taskProvider->append(childTask12); childTaskProvider->append(childTask11); childTaskProvider->append(childTask12); // One project auto project = Domain::Project::Ptr::create(); project->setName("KDE"); auto projectProvider = Domain::QueryResultProvider::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project); Utils::MockObject taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(childTaskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task3).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask11).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask12).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(task1).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(task2).thenReturn(projectResult); + taskQueriesMock(&Domain::TaskQueries::findProject).when(task3).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(childTask11).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(childTask12).thenReturn(Domain::QueryResult::Ptr()); Utils::MockObject taskRepositoryMock; Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN QAbstractItemModel *model = workday.centralListModel(); // THEN const QModelIndex task1Index = model->index(0, 0); const QModelIndex task2Index = model->index(1, 0); const QModelIndex task3Index = model->index(2, 0); const QModelIndex taskChildTask12Index = model->index(3, 0); const QModelIndex childTask11Index = model->index(0, 0, task1Index); const QModelIndex childTask12Index = model->index(1, 0, task1Index); QCOMPARE(model->rowCount(), 4); QCOMPARE(model->rowCount(task1Index), 2); QCOMPARE(model->rowCount(task2Index), 0); QCOMPARE(model->rowCount(task3Index), 0); QCOMPARE(model->rowCount(taskChildTask12Index), 0); QVERIFY(childTask11Index.isValid()); QVERIFY(childTask12Index.isValid()); QCOMPARE(model->rowCount(childTask11Index), 0); QCOMPARE(model->rowCount(childTask12Index), 0); const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; QCOMPARE(model->flags(task1Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(childTask11Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(childTask12Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(task2Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(task3Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(taskChildTask12Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->data(task1Index).toString(), task1->title()); QCOMPARE(model->data(childTask11Index).toString(), childTask11->title()); QCOMPARE(model->data(childTask12Index).toString(), childTask12->title()); QCOMPARE(model->data(task2Index).toString(), task2->title()); QCOMPARE(model->data(task3Index).toString(), task3->title()); QCOMPARE(model->data(taskChildTask12Index).toString(), childTask12->title()); QCOMPARE(model->data(task1Index, Qt::EditRole).toString(), task1->title()); QCOMPARE(model->data(childTask11Index, Qt::EditRole).toString(), childTask11->title()); QCOMPARE(model->data(childTask12Index, Qt::EditRole).toString(), childTask12->title()); QCOMPARE(model->data(task2Index, Qt::EditRole).toString(), task2->title()); QCOMPARE(model->data(task3Index, Qt::EditRole).toString(), task3->title()); QCOMPARE(model->data(taskChildTask12Index, Qt::EditRole).toString(), childTask12->title()); QVERIFY(model->data(task1Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(childTask11Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(childTask12Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(task2Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(task3Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(taskChildTask12Index, Qt::CheckStateRole).isValid()); QCOMPARE(model->data(task1Index, Qt::CheckStateRole).toBool(), task1->isDone()); QCOMPARE(model->data(childTask11Index, Qt::CheckStateRole).toBool(), childTask11->isDone()); QCOMPARE(model->data(childTask12Index, Qt::CheckStateRole).toBool(), childTask12->isDone()); QCOMPARE(model->data(task2Index, Qt::CheckStateRole).toBool(), task2->isDone()); QCOMPARE(model->data(task3Index, Qt::CheckStateRole).toBool(), task3->isDone()); QCOMPARE(model->data(taskChildTask12Index, Qt::CheckStateRole).toBool(), childTask12->isDone()); - QCOMPARE(model->data(task1Index, Presentation::WorkdayPageModel::ProjectRole).toString(), QString("Inbox")); - QCOMPARE(model->data(task2Index, Presentation::WorkdayPageModel::ProjectRole).toString(), QString("Project: KDE")); + QCOMPARE(model->data(task1Index, Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(), QString("Inbox")); + QCOMPARE(model->data(task2Index, Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(), QString("Project: KDE")); // WHEN taskRepositoryMock(&Domain::TaskRepository::update).when(task1).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(childTask11).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(childTask12).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(task2).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(task3).thenReturn(new FakeJob(this)); QVERIFY(model->setData(task1Index, "newTask1")); QVERIFY(model->setData(childTask11Index, "newChildTask11")); QVERIFY(model->setData(task2Index, "newTask2")); QVERIFY(model->setData(task3Index, "newTask3")); QVERIFY(model->setData(taskChildTask12Index, "newChildTask12")); QVERIFY(model->setData(task1Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(childTask11Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(task2Index, Qt::Checked, Qt::CheckStateRole)); QVERIFY(model->setData(task3Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(taskChildTask12Index, Qt::Checked, Qt::CheckStateRole)); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task1).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask11).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask12).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task2).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task3).exactly(2)); QCOMPARE(task1->title(), QStringLiteral("newTask1")); QCOMPARE(childTask11->title(), QStringLiteral("newChildTask11")); QCOMPARE(childTask12->title(), QStringLiteral("newChildTask12")); QCOMPARE(task2->title(), QStringLiteral("newTask2")); QCOMPARE(task3->title(), QStringLiteral("newTask3")); QCOMPARE(task1->isDone(), false); QCOMPARE(childTask11->isDone(), false); QCOMPARE(childTask12->isDone(), true); QCOMPARE(task2->isDone(), true); QCOMPARE(task3->isDone(), false); // WHEN auto data = std::unique_ptr(model->mimeData(QModelIndexList() << childTask12Index)); // THEN QVERIFY(data->hasFormat(QStringLiteral("application/x-zanshin-object"))); QCOMPARE(data->property("objects").value(), Domain::Artifact::List() << childTask12); // WHEN auto childTask2 = Domain::Task::Ptr::create(); taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask11, childTask2).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask2)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, childTask11Index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask11, childTask2).exactly(1)); // WHEN auto childTask3 = Domain::Task::Ptr::create(); auto childTask4 = Domain::Task::Ptr::create(); taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask3).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask4).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask3 << childTask4)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, childTask12Index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask3).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask4).exactly(1)); // WHEN auto childTask5 = Domain::Task::Ptr::create(); QVERIFY(!childTask5->startDate().isValid()); taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask5).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask5)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, QModelIndex()); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask5).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask5).exactly(0)); QCOMPARE(childTask5->startDate(), today); } void shouldAddTasksInWorkdayPage() { // GIVEN // ... in fact we won't list any model Utils::MockObject taskQueriesMock; // We'll gladly create a task though Utils::MockObject taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::create).when(any()).thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN auto title = QStringLiteral("New task"); auto today = Utils::DateTime::currentDate(); auto task = workday.addItem(title).objectCast(); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::create).when(any()).exactly(1)); QVERIFY(task); QCOMPARE(task->title(), title); QCOMPARE(task->startDate(), today); } void shouldAddChildTask() { // GIVEN // Two tasks auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider::Ptr::create(); auto taskResult = Domain::QueryResult::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); Utils::MockObject taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(any()).thenReturn(Domain::QueryResult::Ptr()); Utils::MockObject taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), any()) .thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const auto title = QStringLiteral("New task"); const auto parentIndex = workday.centralListModel()->index(0, 0); const auto createdTask = workday.addItem(title, parentIndex).objectCast(); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), any()) .exactly(1)); QVERIFY(createdTask); QCOMPARE(createdTask->title(), title); QVERIFY(!createdTask->startDate().isValid()); } void shouldDeleteItems() { // GIVEN // Two tasks auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider::Ptr::create(); auto taskResult = Domain::QueryResult::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); Utils::MockObject taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(any()).thenReturn(Domain::QueryResult::Ptr()); Utils::MockObject taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::remove).when(task2).thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const QModelIndex index = workday.centralListModel()->index(1, 0); workday.removeItem(index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::remove).when(task2).exactly(1)); } void shouldPromoteItem() { // GIVEN // Two tasks auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider::Ptr::create(); auto taskResult = Domain::QueryResult::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); Utils::MockObject taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(any()).thenReturn(Domain::QueryResult::Ptr()); Utils::MockObject taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task2).thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const QModelIndex index = workday.centralListModel()->index(1, 0); workday.promoteItem(index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task2).exactly(1)); } }; ZANSHIN_TEST_MAIN(WorkdayPageModelTest) #include "workdaypagemodeltest.moc"