diff --git a/src/presentation/availablesourcesmodel.h b/src/presentation/availablesourcesmodel.h --- a/src/presentation/availablesourcesmodel.h +++ b/src/presentation/availablesourcesmodel.h @@ -32,6 +32,7 @@ #include "presentation/metatypes.h" #include "presentation/errorhandlingmodelbase.h" +#include "presentation/querytreemodelbase.h" class QModelIndex; @@ -44,6 +45,11 @@ Q_PROPERTY(QAbstractItemModel* searchListModel READ searchListModel) Q_PROPERTY(QString searchTerm READ searchTerm WRITE setSearchTerm NOTIFY searchTermChanged) public: + enum { + IsDefaultRole = QueryTreeModelBase::UserRole + 1, + UserRole + }; + explicit AvailableSourcesModel(const Domain::DataSourceQueries::Ptr &dataSourceQueries, const Domain::DataSourceRepository::Ptr &dataSourceRepository, QObject *parent = Q_NULLPTR); @@ -58,10 +64,15 @@ void searchTermChanged(const QString &term); public slots: + void setDefaultItem(const QModelIndex &index); + void listSource(const Domain::DataSource::Ptr &source); void unlistSource(const Domain::DataSource::Ptr &source); void bookmarkSource(const Domain::DataSource::Ptr &source); +private slots: + void onDefaultSourceChanged(const QModelIndex &root = QModelIndex()); + private: QAbstractItemModel *createSourceListModel(); QAbstractItemModel *createSearchListModel(); diff --git a/src/presentation/availablesourcesmodel.cpp b/src/presentation/availablesourcesmodel.cpp --- a/src/presentation/availablesourcesmodel.cpp +++ b/src/presentation/availablesourcesmodel.cpp @@ -105,12 +105,13 @@ return defaultFlags; }; - auto data = [] (const Domain::DataSource::Ptr &source, int role) -> QVariant { + auto data = [this] (const Domain::DataSource::Ptr &source, int role) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::DecorationRole && role != Qt::CheckStateRole - && role != QueryTreeModelBase::IconNameRole) { + && role != QueryTreeModelBase::IconNameRole + && role != IsDefaultRole) { return QVariant(); } @@ -128,6 +129,8 @@ return source->isSelected() ? Qt::Checked : Qt::Unchecked; else return QVariant(); + } else if (role == IsDefaultRole) { + return m_dataSourceQueries->isDefaultSource(source); } else { return QVariant(); } @@ -155,6 +158,8 @@ return Q_NULLPTR; }; + connect(m_dataSourceQueries->notifier(), SIGNAL(defaultSourceChanged()), + this, SLOT(onDefaultSourceChanged())); return new QueryTreeModel(query, flags, data, setData, drop, drag, this); } @@ -227,3 +232,21 @@ m_dataSourceQueries->setSearchTerm(term); emit searchTermChanged(term); } + +void AvailableSourcesModel::setDefaultItem(const QModelIndex &index) +{ + auto source = index.data(QueryTreeModelBase::ObjectRole).value(); + Q_ASSERT(source); + m_dataSourceQueries->setDefaultSource(source); +} + +void AvailableSourcesModel::onDefaultSourceChanged(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); + // TODO Qt5: Remove static_cast + emit static_cast(m_sourceListModel)->dataChanged(index, index); + onDefaultSourceChanged(index); + } +} diff --git a/src/presentation/querytreemodelbase.h b/src/presentation/querytreemodelbase.h --- a/src/presentation/querytreemodelbase.h +++ b/src/presentation/querytreemodelbase.h @@ -90,6 +90,9 @@ QMimeData *mimeData(const QModelIndexList &indexes) const Q_DECL_OVERRIDE; QStringList mimeTypes() const Q_DECL_OVERRIDE; + // TODO Qt5: Remove but needed in Qt4, so that we can trigger it from the outside + using QAbstractItemModel::dataChanged; + protected: explicit QueryTreeModelBase(QueryTreeNodeBase *rootNode, QObject *parent = Q_NULLPTR); diff --git a/tests/units/presentation/availablesourcesmodeltest.cpp b/tests/units/presentation/availablesourcesmodeltest.cpp --- a/tests/units/presentation/availablesourcesmodeltest.cpp +++ b/tests/units/presentation/availablesourcesmodeltest.cpp @@ -25,6 +25,8 @@ #include "utils/mockobject.h" +#define ZANSHIN_I_SWEAR_I_AM_IN_A_PRESENTATION_TEST + #include "domain/datasourcequeries.h" #include "domain/datasourcerepository.h" @@ -34,6 +36,8 @@ #include "testlib/fakejob.h" +Q_DECLARE_METATYPE(QModelIndex); + using namespace mockitopp; using namespace mockitopp::matcher; @@ -51,6 +55,13 @@ class AvailableSourcesModelTest : public QObject { Q_OBJECT +public: + explicit AvailableSourcesModelTest(QObject *parent = Q_NULLPTR) + : QObject(parent) + { + qRegisterMetaType(); + } + private slots: void shouldListAvailableSources() { @@ -99,6 +110,14 @@ sourceQueriesMock(&Domain::DataSourceQueries::findChildren).when(source2).thenReturn(source2Result); sourceQueriesMock(&Domain::DataSourceQueries::findChildren).when(source3).thenReturn(source3Result); sourceQueriesMock(&Domain::DataSourceQueries::findChildren).when(source4).thenReturn(source4Result); + // We'll simulate a default source change later on + sourceQueriesMock(&Domain::DataSourceQueries::isDefaultSource).when(source1).thenReturn(false); + sourceQueriesMock(&Domain::DataSourceQueries::isDefaultSource).when(source2).thenReturn(true) + .thenReturn(false); + sourceQueriesMock(&Domain::DataSourceQueries::isDefaultSource).when(source3).thenReturn(false); + sourceQueriesMock(&Domain::DataSourceQueries::isDefaultSource).when(source4).thenReturn(false) + .thenReturn(false) + .thenReturn(true); Utils::MockObject sourceRepositoryMock; @@ -148,6 +167,11 @@ QCOMPARE(model->data(source3Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QString("folder")); QCOMPARE(model->data(source4Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QString("folder")); + QCOMPARE(model->data(source1Index, Presentation::AvailableSourcesModel::IsDefaultRole).toBool(), false); + QCOMPARE(model->data(source2Index, Presentation::AvailableSourcesModel::IsDefaultRole).toBool(), true); + QCOMPARE(model->data(source3Index, Presentation::AvailableSourcesModel::IsDefaultRole).toBool(), false); + QCOMPARE(model->data(source4Index, Presentation::AvailableSourcesModel::IsDefaultRole).toBool(), false); + // WHEN sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source2).thenReturn(new FakeJob(this)); sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source4).thenReturn(new FakeJob(this)); @@ -162,6 +186,30 @@ QVERIFY(source2->isSelected()); QVERIFY(!source4->isSelected()); + + // WHEN + QSignalSpy spy(model, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + sourceQueriesMock(&Domain::DataSourceQueries::changeDefaultSource).when(source4).thenReturn(); + sources.setDefaultItem(source4Index); + + // THEN + QCOMPARE(model->data(source1Index, Presentation::AvailableSourcesModel::IsDefaultRole).toBool(), false); + QCOMPARE(model->data(source2Index, Presentation::AvailableSourcesModel::IsDefaultRole).toBool(), false); + QCOMPARE(model->data(source3Index, Presentation::AvailableSourcesModel::IsDefaultRole).toBool(), false); + QCOMPARE(model->data(source4Index, Presentation::AvailableSourcesModel::IsDefaultRole).toBool(), true); + + // Not overly efficient way of signaling the change, but doesn't happen often + QCOMPARE(spy.count(), 4); + QCOMPARE(spy.at(0).at(0).value(), source1Index); + QCOMPARE(spy.at(0).at(1).value(), source1Index); + QCOMPARE(spy.at(1).at(0).value(), source3Index); + QCOMPARE(spy.at(1).at(1).value(), source3Index); + QCOMPARE(spy.at(2).at(0).value(), source4Index); + QCOMPARE(spy.at(2).at(1).value(), source4Index); + QCOMPARE(spy.at(3).at(0).value(), source2Index); + QCOMPARE(spy.at(3).at(1).value(), source2Index); + + QVERIFY(sourceQueriesMock(&Domain::DataSourceQueries::changeDefaultSource).when(source4).exactly(1)); } void shouldListAvailableSearchSources()