diff --git a/src/widgets/pageview.cpp b/src/widgets/pageview.cpp index fcbfac4f..c8f47613 100644 --- a/src/widgets/pageview.cpp +++ b/src/widgets/pageview.cpp @@ -1,466 +1,475 @@ /* 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 "pageview.h" #include #include #include #include #include #include #include #include #include #include #include #include "filterwidget.h" #include "itemdelegate.h" #include "messagebox.h" #include #include "presentation/artifactfilterproxymodel.h" #include "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "presentation/runningtaskmodelinterface.h" namespace Widgets { class PageTreeView : public QTreeView { Q_OBJECT public: using QTreeView::QTreeView; protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE { if (event->key() == Qt::Key_Escape && state() != EditingState) { selectionModel()->clear(); } QTreeView::keyPressEvent(event); } void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE { header()->resizeSection(0, event->size().width()); QTreeView::resizeEvent(event); } }; } class PassivePopup : public QFrame { Q_OBJECT public: explicit PassivePopup(QWidget *parent = Q_NULLPTR) : QFrame(parent), m_hideTimer(new QTimer(this)), m_label(new QLabel(this)) { setWindowFlags(Qt::Tool | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint); setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(2); setAttribute(Qt::WA_DeleteOnClose); setLayout(new QVBoxLayout); layout()->addWidget(m_label); connect(m_hideTimer, &QTimer::timeout, this, &QWidget::hide); } void setVisible(bool visible) Q_DECL_OVERRIDE { if (visible) { m_hideTimer->start(2000); } QFrame::setVisible(visible); } void setText(const QString &text) { m_label->setText(text); } private: QTimer *m_hideTimer; QLabel *m_label; }; using namespace Widgets; PageView::PageView(QWidget *parent) : QWidget(parent), m_cancelAction(new QAction(this)), m_model(Q_NULLPTR), m_messageWidget(new KMessageWidget(this)), m_filterWidget(new FilterWidget(this)), m_centralView(new PageTreeView(this)), m_quickAddEdit(new QLineEdit(this)) { m_messageWidget->setObjectName(QStringLiteral("messageWidget")); m_messageWidget->setCloseButtonVisible(true); m_messageWidget->setMessageType(KMessageWidget::Error); m_messageWidget->setWordWrap(true); m_messageWidget->hide(); m_filterWidget->setObjectName(QStringLiteral("filterWidget")); m_filterWidget->hide(); m_centralView->setObjectName(QStringLiteral("centralView")); m_centralView->header()->hide(); m_centralView->setAlternatingRowColors(true); m_centralView->setItemDelegate(new ItemDelegate(this)); m_centralView->setDragDropMode(QTreeView::DragDrop); m_centralView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_centralView->setModel(m_filterWidget->proxyModel()); m_centralView->installEventFilter(this); m_centralView->setItemsExpandable(false); m_centralView->setRootIsDecorated(false); connect(m_centralView->model(), &QAbstractItemModel::rowsInserted, m_centralView, &QTreeView::expandAll); connect(m_centralView->model(), &QAbstractItemModel::layoutChanged, m_centralView, &QTreeView::expandAll); connect(m_centralView->model(), &QAbstractItemModel::modelReset, m_centralView, &QTreeView::expandAll); m_centralView->setStyleSheet(QStringLiteral("QTreeView::branch { border-image: url(none.png); }")); m_quickAddEdit->setObjectName(QStringLiteral("quickAddEdit")); m_quickAddEdit->setPlaceholderText(i18n("Type and press enter to add an item")); connect(m_quickAddEdit, &QLineEdit::returnPressed, this, &PageView::onReturnPressed); auto layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 3); layout->addWidget(m_messageWidget); layout->addWidget(m_filterWidget); layout->addWidget(m_centralView); layout->addWidget(m_quickAddEdit); setLayout(layout); m_messageBoxInterface = MessageBox::Ptr::create(); auto addItemAction = new QAction(this); addItemAction->setObjectName(QStringLiteral("addItemAction")); addItemAction->setText(i18n("New Item")); addItemAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addItemAction->setShortcut(Qt::CTRL | Qt::Key_N); connect(addItemAction, &QAction::triggered, this, &PageView::onAddItemRequested); m_cancelAction->setObjectName(QStringLiteral("cancelAddItemAction")); m_cancelAction->setShortcut(Qt::Key_Escape); addAction(m_cancelAction); connect(m_cancelAction, &QAction::triggered, m_centralView, static_cast(&QWidget::setFocus)); auto removeItemAction = new QAction(this); removeItemAction->setObjectName(QStringLiteral("removeItemAction")); removeItemAction->setText(i18n("Remove Item")); removeItemAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); removeItemAction->setShortcut(Qt::Key_Delete); connect(removeItemAction, &QAction::triggered, this, &PageView::onRemoveItemRequested); addAction(removeItemAction); auto promoteItemAction = new QAction(this); promoteItemAction->setObjectName(QStringLiteral("promoteItemAction")); promoteItemAction->setText(i18n("Promote Item as Project")); promoteItemAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P); connect(promoteItemAction, &QAction::triggered, this, &PageView::onPromoteItemRequested); auto filterViewAction = new QAction(this); filterViewAction->setObjectName(QStringLiteral("filterViewAction")); filterViewAction->setText(i18n("Filter...")); filterViewAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); filterViewAction->setShortcut(Qt::CTRL | Qt::Key_F); filterViewAction->setCheckable(true); connect(filterViewAction, &QAction::triggered, this, &PageView::onFilterToggled); + auto futureViewAction = new QAction(this); + futureViewAction->setObjectName(QStringLiteral("futureViewAction")); + futureViewAction->setText(i18n("Show future items")); + futureViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-calendar-whatsnext"))); + futureViewAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_F); + futureViewAction->setCheckable(true); + connect(futureViewAction, &QAction::triggered, m_filterWidget, &FilterWidget::setShowFutureTasks); + m_runTaskAction = new QAction(this); m_runTaskAction->setObjectName(QStringLiteral("runTaskAction")); m_runTaskAction->setShortcut(Qt::CTRL | Qt::Key_Space); m_runTaskAction->setText(i18n("Start Now")); m_runTaskAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); connect(m_runTaskAction, &QAction::triggered, this, &PageView::onRunTaskTriggered); updateRunTaskAction(); m_actions.insert(QStringLiteral("page_view_add"), addItemAction); m_actions.insert(QStringLiteral("page_view_remove"), removeItemAction); m_actions.insert(QStringLiteral("page_view_promote"), promoteItemAction); m_actions.insert(QStringLiteral("page_view_filter"), filterViewAction); + m_actions.insert(QStringLiteral("page_view_future"), futureViewAction); m_actions.insert(QStringLiteral("page_run_task"), m_runTaskAction); } QHash PageView::globalActions() const { return m_actions; } QObject *PageView::model() const { return m_model; } Presentation::RunningTaskModelInterface *PageView::runningTaskModel() const { return m_runningTaskModel; } void PageView::setModel(QObject *model) { if (model == m_model) return; if (m_centralView->selectionModel()) { disconnect(m_centralView->selectionModel(), Q_NULLPTR, this, Q_NULLPTR); } m_filterWidget->proxyModel()->setSourceModel(Q_NULLPTR); m_model = model; setEnabled(m_model); updateRunTaskAction(); if (!m_model) return; QVariant modelProperty = m_model->property("centralListModel"); if (modelProperty.canConvert()) m_filterWidget->proxyModel()->setSourceModel(modelProperty.value()); connect(m_centralView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PageView::onCurrentChanged); } MessageBoxInterface::Ptr PageView::messageBoxInterface() const { return m_messageBoxInterface; } QModelIndexList PageView::selectedIndexes() const { using namespace std::placeholders; const auto selection = m_centralView->selectionModel()->selectedIndexes(); auto sourceIndices = QModelIndexList(); std::transform(selection.constBegin(), selection.constEnd(), std::back_inserter(sourceIndices ), std::bind(&QSortFilterProxyModel::mapToSource, m_filterWidget->proxyModel(), _1)); return sourceIndices; } void PageView::setRunningTaskModel(Presentation::RunningTaskModelInterface *model) { m_runningTaskModel = model; connect(m_runningTaskModel, SIGNAL(runningTaskChanged(Domain::Task::Ptr)), this, SLOT(onRunningTaskChanged(Domain::Task::Ptr))); } void PageView::setMessageBoxInterface(const MessageBoxInterface::Ptr &interface) { m_messageBoxInterface = interface; } void PageView::displayErrorMessage(const QString &message) { m_messageWidget->setText(message); m_messageWidget->animatedShow(); } void PageView::onReturnPressed() { if (m_quickAddEdit->text().isEmpty()) return; auto parentIndex = QModelIndex(); if (m_centralView->selectionModel()->selectedIndexes().size() == 1) parentIndex = m_centralView->selectionModel()->selectedIndexes().first(); QMetaObject::invokeMethod(m_model, "addItem", Q_ARG(QString, m_quickAddEdit->text()), Q_ARG(QModelIndex, parentIndex)); m_quickAddEdit->clear(); } void PageView::onAddItemRequested() { if (m_quickAddEdit->hasFocus()) return; const auto editTopLeft = m_quickAddEdit->geometry().topLeft(); const auto pos = mapToGlobal(editTopLeft); auto popup = new PassivePopup(m_quickAddEdit); popup->setText(i18n("Type and press enter to add an item")); popup->show(); popup->move(pos - QPoint(0, popup->height())); m_quickAddEdit->selectAll(); m_quickAddEdit->setFocus(); } void PageView::onRemoveItemRequested() { const QModelIndexList ¤tIndexes = m_centralView->selectionModel()->selectedIndexes(); if (currentIndexes.isEmpty()) return; QString text; if (currentIndexes.size() > 1) { bool hasDescendants = false; foreach (const QModelIndex ¤tIndex, currentIndexes) { if (!currentIndex.isValid()) continue; if (currentIndex.model()->rowCount(currentIndex) > 0) { hasDescendants = true; break; } } if (hasDescendants) text = i18n("Do you really want to delete the selected items and their children?"); else text = i18n("Do you really want to delete the selected items?"); } else { const QModelIndex ¤tIndex = currentIndexes.first(); if (!currentIndex.isValid()) return; if (currentIndex.model()->rowCount(currentIndex) > 0) text = i18n("Do you really want to delete the selected task and all its children?"); } if (!text.isEmpty()) { QMessageBox::Button button = m_messageBoxInterface->askConfirmation(this, i18n("Delete Tasks"), text); bool canRemove = (button == QMessageBox::Yes); if (!canRemove) return; } foreach (const QModelIndex ¤tIndex, currentIndexes) { if (!currentIndex.isValid()) continue; QMetaObject::invokeMethod(m_model, "removeItem", Q_ARG(QModelIndex, currentIndex)); const auto data = currentIndex.data(Presentation::QueryTreeModelBase::ObjectRole); if (data.isValid()) { auto task = data.value().objectCast(); if (task) m_runningTaskModel->taskDeleted(task); } } } void PageView::onPromoteItemRequested() { QModelIndex currentIndex = m_centralView->currentIndex(); if (!currentIndex.isValid()) return; QMetaObject::invokeMethod(m_model, "promoteItem", Q_ARG(QModelIndex, currentIndex)); } void PageView::onFilterToggled(bool show) { m_filterWidget->setVisible(show); if (show) m_filterWidget->setFocus(); else m_filterWidget->clear(); } void PageView::updateRunTaskAction() { const auto artifact = currentArtifact(); const auto task = artifact.objectCast(); m_runTaskAction->setEnabled(task); } void PageView::onRunTaskTriggered() { auto task = currentArtifact().objectCast(); Q_ASSERT(task); // the action is supposed to be disabled otherwise if (task->startDate().isNull()) task->setStartDate(QDateTime::currentDateTime()); m_runningTaskModel->setRunningTask(task); } void PageView::onRunningTaskChanged(const Domain::Task::Ptr &task) { if (!task) { QWidget *toplevel = window(); toplevel->raise(); toplevel->activateWindow(); } } void PageView::onCurrentChanged(const QModelIndex ¤t) { updateRunTaskAction(); auto data = current.data(Presentation::QueryTreeModelBase::ObjectRole); if (!data.isValid()) return; auto artifact = currentArtifact(); if (!artifact) return; emit currentArtifactChanged(artifact); } bool PageView::eventFilter(QObject *object, QEvent *event) { Q_ASSERT(object == m_centralView); switch(event->type()) { case QEvent::FocusIn: m_cancelAction->setEnabled(false); break; case QEvent::FocusOut: m_cancelAction->setEnabled(true); break; default: break; } return false; } Domain::Artifact::Ptr PageView::currentArtifact() const { const auto current = m_centralView->selectionModel()->currentIndex(); const auto data = current.data(Presentation::QueryTreeModelBase::ObjectRole); if (!data.isValid()) return Domain::Artifact::Ptr(); return data.value(); } #include "pageview.moc" diff --git a/src/zanshin/app/zanshinui.rc b/src/zanshin/app/zanshinui.rc index 53143314..53d29f93 100644 --- a/src/zanshin/app/zanshinui.rc +++ b/src/zanshin/app/zanshinui.rc @@ -1,43 +1,45 @@ &Go + &Panels Main Toolbar + diff --git a/tests/units/widgets/pageviewtest.cpp b/tests/units/widgets/pageviewtest.cpp index 857fb094..bdc9df3d 100644 --- a/tests/units/widgets/pageviewtest.cpp +++ b/tests/units/widgets/pageviewtest.cpp @@ -1,748 +1,778 @@ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "domain/task.h" #include "presentation/artifactfilterproxymodel.h" #include "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "widgets/filterwidget.h" #include "widgets/itemdelegate.h" #include "widgets/pageview.h" #include "messageboxstub.h" class PageModelStub : public QObject { Q_OBJECT Q_PROPERTY(QAbstractItemModel* centralListModel READ centralListModel) public: QAbstractItemModel *centralListModel() { return &itemModel; } QStandardItem *addStubItem(const QString &title, QStandardItem *parentItem = Q_NULLPTR) { QStandardItem *item = new QStandardItem; item->setData(title, Qt::DisplayRole); if (!parentItem) itemModel.appendRow(item); else parentItem->appendRow(item); taskNames << title; return item; } Domain::Task::Ptr addTaskItem(const QString &title, QStandardItem *parentItem = Q_NULLPTR) { auto item = addStubItem(title, parentItem); auto task = Domain::Task::Ptr::create(); task->setTitle(title); item->setData(QVariant::fromValue(task), Presentation::QueryTreeModelBase::ObjectRole); return task; } void addStubItems(const QStringList &list) { foreach (const QString &title, list) { addStubItem(title); } } public slots: void addItem(const QString &name, const QModelIndex &parentIndex) { taskNames << name; parentIndices << parentIndex; } void removeItem(const QModelIndex &index) { removedIndices << index; } void promoteItem(const QModelIndex &index) { promotedIndices << index; } public: QStringList taskNames; QList parentIndices; QList removedIndices; QList promotedIndices; QStandardItemModel itemModel; }; class RunningTaskModelStub : public Presentation::RunningTaskModelInterface { Q_OBJECT public: Domain::Task::Ptr runningTask() const Q_DECL_OVERRIDE { return m_runningTask; } void setRunningTask(const Domain::Task::Ptr &task) Q_DECL_OVERRIDE { m_runningTask = task; } void taskDeleted(const Domain::Task::Ptr &task) Q_DECL_OVERRIDE { m_deletedTask = task; } void stopTask() Q_DECL_OVERRIDE {} void doneTask() Q_DECL_OVERRIDE {} private: Domain::Task::Ptr m_runningTask; Domain::Task::Ptr m_deletedTask; }; class PageViewTest : public QObject { Q_OBJECT private slots: void shouldHaveDefaultState() { Widgets::PageView page; QCOMPARE(page.contentsMargins(), QMargins(0, 0, 0, 0)); QCOMPARE(page.layout()->contentsMargins(), QMargins(0, 0, 0, 3)); auto messageWidget = page.findChild(QStringLiteral("messageWidget")); QVERIFY(messageWidget); QVERIFY(!messageWidget->isVisibleTo(&page)); QVERIFY(!messageWidget->isCloseButtonVisible()); QVERIFY(messageWidget->wordWrap()); QVERIFY(messageWidget->text().isEmpty()); QVERIFY(messageWidget->icon().isNull()); QCOMPARE(messageWidget->messageType(), KMessageWidget::Error); QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); auto centralView = page.findChild(QStringLiteral("centralView")); QVERIFY(centralView); QVERIFY(centralView->isVisibleTo(&page)); QVERIFY(!centralView->header()->isVisibleTo(&page)); QVERIFY(qobject_cast(centralView->itemDelegate())); QVERIFY(centralView->alternatingRowColors()); QCOMPARE(centralView->dragDropMode(), QTreeView::DragDrop); auto filter = page.findChild(QStringLiteral("filterWidget")); QVERIFY(filter); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(filter->proxyModel()); QCOMPARE(filter->proxyModel(), centralView->model()); QLineEdit *quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); QVERIFY(quickAddEdit); QVERIFY(quickAddEdit->isVisibleTo(&page)); QVERIFY(quickAddEdit->text().isEmpty()); QCOMPARE(quickAddEdit->placeholderText(), i18n("Type and press enter to add an item")); auto addAction = page.findChild(QStringLiteral("addItemAction")); QVERIFY(addAction); auto cancelAddAction = page.findChild(QStringLiteral("cancelAddItemAction")); QVERIFY(cancelAddAction); auto removeAction = page.findChild(QStringLiteral("removeItemAction")); QVERIFY(removeAction); auto promoteAction = page.findChild(QStringLiteral("promoteItemAction")); QVERIFY(promoteAction); auto filterAction = page.findChild(QStringLiteral("filterViewAction")); QVERIFY(filterAction); QVERIFY(filterAction->isCheckable()); QVERIFY(!filterAction->isChecked()); + auto futureAction = page.findChild(QStringLiteral("futureViewAction")); + QVERIFY(futureAction); + QVERIFY(futureAction->isCheckable()); + QVERIFY(!futureAction->isChecked()); auto runTaskAction = page.findChild(QStringLiteral("runTaskAction")); QVERIFY(runTaskAction); QVERIFY(!runTaskAction->isEnabled()); auto actions = page.globalActions(); QCOMPARE(actions.value(QStringLiteral("page_view_add")), addAction); QCOMPARE(actions.value(QStringLiteral("page_view_remove")), removeAction); QCOMPARE(actions.value(QStringLiteral("page_view_promote")), promoteAction); QCOMPARE(actions.value(QStringLiteral("page_view_filter")), filterAction); + QCOMPARE(actions.value(QStringLiteral("page_view_future")), futureAction); QCOMPARE(actions.value(QStringLiteral("page_run_task")), runTaskAction); } void shouldDisplayListFromPageModel() { // GIVEN QStandardItemModel model; QObject stubPageModel; stubPageModel.setProperty("centralListModel", QVariant::fromValue(static_cast(&model))); Widgets::PageView page; auto centralView = page.findChild(QStringLiteral("centralView")); QVERIFY(centralView); auto proxyModel = qobject_cast(centralView->model()); QVERIFY(proxyModel); QVERIFY(!proxyModel->sourceModel()); // WHEN page.setModel(&stubPageModel); // THEN QCOMPARE(page.model(), &stubPageModel); QVERIFY(page.isEnabled()); QCOMPARE(proxyModel->sourceModel(), &model); } void shouldNotCrashWithNullModel() { // GIVEN QStandardItemModel model; QObject stubPageModel; stubPageModel.setProperty("centralListModel", QVariant::fromValue(static_cast(&model))); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); QVERIFY(centralView); auto proxyModel = qobject_cast(centralView->model()); QVERIFY(proxyModel); QCOMPARE(proxyModel->sourceModel(), &model); // WHEN page.setModel(Q_NULLPTR); // THEN QVERIFY(!page.model()); QVERIFY(!page.isEnabled()); QVERIFY(!proxyModel->sourceModel()); } void shouldManageFocusThroughActions() { // GIVEN Widgets::PageView page; auto centralView = page.findChild(QStringLiteral("centralView")); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); auto filter = page.findChild(QStringLiteral("filterWidget")); auto filterEdit = filter->findChild(); QVERIFY(filterEdit); page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); centralView->setFocus(); QVERIFY(centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); auto addAction = page.findChild(QStringLiteral("addItemAction")); auto cancelAddAction = page.findChild(QStringLiteral("cancelAddItemAction")); auto filterAction = page.findChild(QStringLiteral("filterViewAction")); // WHEN addAction->trigger(); // THEN QVERIFY(!centralView->hasFocus()); QVERIFY(quickAddEdit->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); // WHEN cancelAddAction->trigger(); // THEN QVERIFY(centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); // WHEN filterAction->trigger(); // THEN QVERIFY(!centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(filter->isVisibleTo(&page)); QVERIFY(filterEdit->hasFocus()); // WHEN cancelAddAction->trigger(); // THEN QVERIFY(centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); } void shouldManageFilterVisibilityThroughAction() { // GIVEN Widgets::PageView page; auto centralView = page.findChild(QStringLiteral("centralView")); auto filter = page.findChild(QStringLiteral("filterWidget")); auto filterEdit = filter->findChild(); QVERIFY(filterEdit); page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); centralView->setFocus(); QVERIFY(centralView->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); auto filterAction = page.findChild(QStringLiteral("filterViewAction")); // WHEN filterAction->trigger(); // THEN QVERIFY(!centralView->hasFocus()); QVERIFY(filter->isVisibleTo(&page)); QVERIFY(filterEdit->hasFocus()); // WHEN filterEdit->setText("Foo"); // THEN QCOMPARE(filterEdit->text(), QString("Foo")); // WHEN filterAction->trigger(); // THEN QVERIFY(centralView->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); QVERIFY(filterEdit->text().isEmpty()); } + void shouldManageFutureTasksVisibilityThroughAction() + { + // GIVEN + Widgets::PageView page; + auto filter = page.findChild(QStringLiteral("filterWidget")); + auto filterProxy = filter->proxyModel(); + QVERIFY(filterProxy); + + QVERIFY(!filterProxy->showFutureTasks()); + + auto futureAction = page.findChild(QStringLiteral("futureViewAction")); + + // WHEN + futureAction->trigger(); + + // THEN + QVERIFY(filterProxy->showFutureTasks()); + + // WHEN + futureAction->trigger(); + + // THEN + QVERIFY(!filterProxy->showFutureTasks()); + } + void shouldCreateTasksWithNoParentWhenHittingReturnWithoutSelectedIndex() { // GIVEN PageModelStub stubPageModel; Widgets::PageView page; page.setModel(&stubPageModel); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); // WHEN QTest::keyClick(quickAddEdit, Qt::Key_Return); // Does nothing (edit empty) QTest::keyClicks(quickAddEdit, QStringLiteral("Foo")); QTest::keyClick(quickAddEdit, Qt::Key_Return); QTest::keyClick(quickAddEdit, Qt::Key_Return); // Does nothing (edit empty) QTest::keyClicks(quickAddEdit, QStringLiteral("Bar")); QTest::keyClick(quickAddEdit, Qt::Key_Return); QTest::keyClick(quickAddEdit, Qt::Key_Return); // Does nothing (edit empty) // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << QStringLiteral("Foo") << QStringLiteral("Bar")); QCOMPARE(stubPageModel.parentIndices.size(), 2); QCOMPARE(stubPageModel.parentIndices.first(), QPersistentModelIndex()); QCOMPARE(stubPageModel.parentIndices.last(), QPersistentModelIndex()); } void shouldCreateTasksWithNoParentWhenHittingReturnWithSeveralSelectedIndices() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index0 = stubPageModel.itemModel.index(0, 0); QPersistentModelIndex index1 = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->select(index0, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->select(index1, QItemSelectionModel::Select); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); // WHEN QTest::keyClicks(quickAddEdit, QStringLiteral("Foo")); QTest::keyClick(quickAddEdit, Qt::Key_Return); // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("Foo")); QCOMPARE(stubPageModel.parentIndices.size(), 1); QCOMPARE(stubPageModel.parentIndices.first(), QPersistentModelIndex()); } void shouldCreateTasksWithParentWhenHittingReturnWithOneSelectedIndex() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); // WHEN QTest::keyClicks(quickAddEdit, QStringLiteral("Foo")); QTest::keyClick(quickAddEdit, Qt::Key_Return); // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("Foo")); QCOMPARE(stubPageModel.parentIndices.size(), 1); QCOMPARE(stubPageModel.parentIndices.first(), index); } void shouldDeleteItemWhenHittingTheDeleteKey() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); centralView->setFocus(); // Needed for shortcuts to work page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QCOMPARE(stubPageModel.removedIndices.size(), 1); QCOMPARE(stubPageModel.removedIndices.first(), index); } void shouldNoteTryToDeleteIfThereIsNoSelection() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); Widgets::PageView page; page.setModel(&stubPageModel); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->clearSelection(); page.findChild(QStringLiteral("quickAddEdit"))->setFocus(); // Needed for shortcuts to work page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QVERIFY(stubPageModel.removedIndices.isEmpty()); } void shouldDisplayNotificationWhenHittingTheDeleteKeyOnAnItemWithChildren() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B")); QStandardItem *parentIndex = stubPageModel.itemModel.item(1, 0); stubPageModel.addStubItem(QStringLiteral("C"), parentIndex); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto msgbox = MessageBoxStub::Ptr::create(); page.setMessageBoxInterface(msgbox); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); QVERIFY(centralView->selectionModel()->currentIndex().isValid()); centralView->setFocus(); // Needed for shortcuts to work page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QVERIFY(msgbox->called()); QCOMPARE(stubPageModel.removedIndices.size(), 1); QCOMPARE(stubPageModel.removedIndices.first(), index); } void shouldDeleteItemsWhenHittingTheDeleteKey() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); QPersistentModelIndex index2 = stubPageModel.itemModel.index(2, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto msgbox = MessageBoxStub::Ptr::create(); page.setMessageBoxInterface(msgbox); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->setCurrentIndex(index2, QItemSelectionModel::Select); centralView->setFocus(); // Needed for shortcuts to work page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QVERIFY(msgbox->called()); QCOMPARE(stubPageModel.removedIndices.size(), 2); QCOMPARE(stubPageModel.removedIndices.first(), index); QCOMPARE(stubPageModel.removedIndices.at(1), index2); } void shouldPromoteItem() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto promoteAction = page.findChild(QStringLiteral("promoteItemAction")); QVERIFY(promoteAction); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); centralView->setFocus(); // WHEN promoteAction->trigger(); // THEN QCOMPARE(stubPageModel.promotedIndices.size(), 1); QCOMPARE(stubPageModel.promotedIndices.first(), index); } void shouldNotTryToPromoteItemIfThereIsNoSelection() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); Widgets::PageView page; page.setModel(&stubPageModel); auto promoteAction = page.findChild(QStringLiteral("promoteItemAction")); QVERIFY(promoteAction); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->clear(); centralView->setFocus(); // WHEN promoteAction->trigger(); // THEN QVERIFY(stubPageModel.promotedIndices.isEmpty()); } void shouldClearCentralViewSelectionOnEscape() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); QVERIFY(!centralView->selectionModel()->selectedIndexes().isEmpty()); // WHEN QTest::keyClick(centralView, Qt::Key_Escape); // THEN QVERIFY(centralView->selectionModel()->selectedIndexes().isEmpty()); } void shouldReturnSelectedIndexes() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); auto index = stubPageModel.itemModel.index(1, 0); auto index2 = stubPageModel.itemModel.index(2, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); auto filterWidget = page.findChild(QStringLiteral("filterWidget")); auto displayedModel = filterWidget->proxyModel(); auto displayedIndex = displayedModel->index(1, 0); auto displayedIndex2 = displayedModel->index(2, 0); // WHEN centralView->selectionModel()->setCurrentIndex(displayedIndex, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->setCurrentIndex(displayedIndex2, QItemSelectionModel::Select); // THEN auto selectedIndexes = page.selectedIndexes(); QCOMPARE(selectedIndexes.size(), 2); QCOMPARE(selectedIndexes.at(0), index); QCOMPARE(selectedIndexes.at(1), index2); QCOMPARE(selectedIndexes.at(0).model(), index.model()); QCOMPARE(selectedIndexes.at(1).model(), index2.model()); } void shouldDisplayMessageOnError() { // GIVEN Widgets::PageView page; page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); auto messageWidget = page.findChild(QStringLiteral("messageWidget")); QVERIFY(messageWidget); QVERIFY(!messageWidget->isVisibleTo(&page)); QCOMPARE(messageWidget->findChildren().size(), 1); auto closeButton = messageWidget->findChildren().first(); QVERIFY(closeButton); // WHEN page.displayErrorMessage(QStringLiteral("Foo Error")); // THEN QVERIFY(messageWidget->isVisibleTo(&page)); QVERIFY(messageWidget->isCloseButtonVisible()); QCOMPARE(messageWidget->text(), QStringLiteral("Foo Error")); QVERIFY(messageWidget->icon().isNull()); QCOMPARE(messageWidget->messageType(), KMessageWidget::Error); QVERIFY(messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); // WHEN QTest::qWait(800); // THEN QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); // WHEN closeButton->click(); // THEN QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(messageWidget->isHideAnimationRunning()); // WHEN QTest::qWait(800); // THEN QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); } void shouldRunTask() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); auto task1 = stubPageModel.addTaskItem(QStringLiteral("Task1")); auto task2 = stubPageModel.addTaskItem(QStringLiteral("Task2")); Widgets::PageView page; page.setModel(&stubPageModel); RunningTaskModelStub stubRunningTaskModel; page.setRunningTaskModel(&stubRunningTaskModel); auto centralView = page.findChild(QStringLiteral("centralView")); QModelIndex index = stubPageModel.itemModel.index(0, 0); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); QVERIFY(centralView->selectionModel()->currentIndex().isValid()); auto runTaskAction = page.findChild(QStringLiteral("runTaskAction")); QVERIFY(runTaskAction); QVERIFY(runTaskAction->isEnabled()); // WHEN starting the first task runTaskAction->trigger(); // THEN QCOMPARE(stubRunningTaskModel.property("runningTask").value(), task1); QCOMPARE(task1->startDate().date(), QDate::currentDate()); // WHEN starting the second task QModelIndex index2 = stubPageModel.itemModel.index(1, 0); centralView->selectionModel()->setCurrentIndex(index2, QItemSelectionModel::ClearAndSelect); runTaskAction->trigger(); // THEN QCOMPARE(stubRunningTaskModel.property("runningTask").value(), task2); QCOMPARE(task2->startDate().date(), QDate::currentDate()); } }; ZANSHIN_TEST_MAIN(PageViewTest) #include "pageviewtest.moc"