diff --git a/app/activeproject.cpp b/app/activeproject.cpp index 2614e9f6a..1a41e464f 100644 --- a/app/activeproject.cpp +++ b/app/activeproject.cpp @@ -29,14 +29,12 @@ const QString ActiveProject::LOADING_FLAG_FILE_PATH = QString( "%1/.input_loadin ActiveProject::ActiveProject( AppSettings &appSettings , ActiveLayer &activeLayer - , LayersProxyModel &recordingLayerPM , LocalProjectsManager &localProjectsManager , QObject *parent ) : QObject( parent ) , mAppSettings( appSettings ) , mActiveLayer( activeLayer ) - , mRecordingLayerPM( recordingLayerPM ) , mLocalProjectsManager( localProjectsManager ) , mProjectLoadingLog( "" ) { @@ -188,7 +186,6 @@ bool ActiveProject::forceLoad( const QString &filePath, bool force ) setProjectRole( role ); updateMapTheme(); - updateRecordingLayers(); updateActiveLayer(); updateMapSettingsLayers(); @@ -391,30 +388,6 @@ AutosyncController *ActiveProject::autosyncController() const return nullptr; } -bool ActiveProject::layerVisible( QgsMapLayer *layer ) -{ - if ( !mQgsProject || !layer ) - { - return false; - } - - QgsLayerTree *root = mQgsProject->layerTreeRoot(); - - if ( !root ) - { - return false; - } - - QgsLayerTreeLayer *layerTree = root->findLayer( layer ); - - if ( !layerTree ) - { - return false; - } - - return layerTree->isVisible(); -} - QString ActiveProject::projectLoadingLog() const { return mProjectLoadingLog; @@ -486,29 +459,38 @@ void ActiveProject::setMapTheme( const QString &themeName ) emit mapThemeChanged( mMapTheme ); - updateRecordingLayers(); // <- worth to decouple similar to map themes model decoupling updateActiveLayer(); updateMapSettingsLayers(); } void ActiveProject::updateActiveLayer() { - if ( !layerVisible( mActiveLayer.layer() ) ) + if ( !InputUtils::layerVisible( mActiveLayer.layer(), mQgsProject ) ) { - QgsMapLayer *defaultLayer = mRecordingLayerPM.layerFromLayerName( mAppSettings.defaultLayer() ); + QgsMapLayer *defaultAppSettingsLayer = InputUtils::mapLayerFromName( mAppSettings.defaultLayer(), mQgsProject ); - if ( !defaultLayer ) + if ( InputUtils::recordingAllowed( defaultAppSettingsLayer, mQgsProject ) ) { - defaultLayer = mRecordingLayerPM.firstUsableLayer(); + setActiveLayer( defaultAppSettingsLayer ); + return; } - setActiveLayer( defaultLayer ); - } -} + // default layer from app settings has no recording allowed => let's try to search for a new one + QgsMapLayer *defaultLayer = nullptr; + const QMap layers = mQgsProject->mapLayers(); + for ( auto it = layers.cbegin(); it != layers.cend(); ++it ) + { + QgsMapLayer *layer = it.value(); + if ( InputUtils::recordingAllowed( layer, mQgsProject ) ) + { + defaultLayer = layer; + break; + } + } -void ActiveProject::updateRecordingLayers() -{ - mRecordingLayerPM.refreshData(); + if ( defaultLayer ) + setActiveLayer( defaultLayer ); + } } bool ActiveProject::isProjectLoaded() const @@ -537,7 +519,6 @@ void ActiveProject::switchLayerTreeNodeVisibility( QgsLayerTreeNode *node ) node->setItemVisibilityChecked( !node->isVisible() ); updateMapTheme(); - updateRecordingLayers(); // <- worth to decouple similar to map themes model decoupling updateActiveLayer(); updateMapSettingsLayers(); } @@ -557,6 +538,19 @@ bool ActiveProject::positionTrackingSupported() const return mQgsProject->readBoolEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PositionTracking/Enabled" ), false ); } +bool ActiveProject::projectHasRecordingLayers() const +{ + if ( !mQgsProject ) + return false; + + const QMap layers = mQgsProject->mapLayers(); + for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it ) + { + if ( InputUtils::recordingAllowed( it.value(), mQgsProject ) ) + return true; + } + + return false; QString ActiveProject::projectRole() const { return mProjectRole; diff --git a/app/activeproject.h b/app/activeproject.h index c9a828fee..0060cac1e 100644 --- a/app/activeproject.h +++ b/app/activeproject.h @@ -43,7 +43,6 @@ class ActiveProject: public QObject explicit ActiveProject( AppSettings &appSettings , ActiveLayer &activeLayer - , LayersProxyModel &recordingLayerPM , LocalProjectsManager &localProjectsManager , QObject *parent = nullptr ); @@ -103,11 +102,6 @@ class ActiveProject: public QObject */ void updateMapSettingsLayers() const; - /** - * layerVisible returns boolean if input layer is visible within current project - */ - bool layerVisible( QgsMapLayer *layer ); - /** * Return the QGIS log recorded during the loading phase of the project */ @@ -120,6 +114,8 @@ class ActiveProject: public QObject bool positionTrackingSupported() const; + //! Returns true if the project has at least one layer that allows recording + Q_INVOKABLE bool projectHasRecordingLayers() const; /** * Returns role/permission level of current user for this project */ @@ -189,7 +185,6 @@ class ActiveProject: public QObject AppSettings &mAppSettings; ActiveLayer &mActiveLayer; - LayersProxyModel &mRecordingLayerPM; LocalProjectsManager &mLocalProjectsManager; InputMapSettings *mMapSettings = nullptr; std::unique_ptr mAutosyncController; diff --git a/app/inpututils.cpp b/app/inpututils.cpp index 218c024e1..3b408a15b 100644 --- a/app/inpututils.cpp +++ b/app/inpututils.cpp @@ -2222,3 +2222,65 @@ double InputUtils::pixelDistanceBetween( const QPointF &p1, const QPointF &p2 ) { return std::hypot( p1.x() - p2.x(), p1.y() - p2.y() ); } + +bool InputUtils::layerHasGeometry( const QgsVectorLayer *layer ) +{ + if ( !layer || !layer->isValid() ) + return false; + return layer->wkbType() != Qgis::WkbType::NoGeometry && layer->wkbType() != Qgis::WkbType::Unknown; +} + +bool InputUtils::layerVisible( QgsMapLayer *layer, QgsProject *project ) +{ + if ( !layer || !layer->isValid() || !project ) + return false; + + QgsLayerTree *root = project->layerTreeRoot(); + + if ( !root ) + return false; + + QgsLayerTreeLayer *layerTree = root->findLayer( layer ); + + if ( layerTree ) + return layerTree->isVisible(); + + return false; +} + +bool InputUtils::isPositionTrackingLayer( QgsMapLayer *layer, QgsProject *project ) +{ + if ( !layer || !project ) + return false; + + QString trackingLayerId = project->readEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PositionTracking/TrackingLayer" ), QString() ); + return layer->id() == trackingLayerId; +} + +bool InputUtils::recordingAllowed( QgsMapLayer *layer, QgsProject *project ) +{ + if ( !layer || !layer->isValid() || !project ) + return false; + + QgsVectorLayer *vectorLayer = qobject_cast( layer ); + + return ( vectorLayer && + !vectorLayer->readOnly() && + layerHasGeometry( vectorLayer ) && + layerVisible( layer, project ) && + !isPositionTrackingLayer( layer, project ) ); +} + +QgsMapLayer *InputUtils::mapLayerFromName( const QString &layerName, QgsProject *project ) +{ + if ( !project || layerName.isEmpty() ) + return nullptr; + + QList layersByName = project->mapLayersByName( layerName ); + if ( !layersByName.isEmpty() ) + { + return layersByName.first(); + } + + return nullptr; +} diff --git a/app/inpututils.h b/app/inpututils.h index 83be81f39..0bebd9829 100644 --- a/app/inpututils.h +++ b/app/inpututils.h @@ -589,6 +589,32 @@ class InputUtils: public QObject */ static double pixelDistanceBetween( const QPointF &p1, const QPointF &p2 ); + /** + * filters if input layer is visible in current map theme + */ + static bool layerVisible( QgsMapLayer *layer, QgsProject *project ); + + /** + * Returns if layer is not NoGeo and not Unknown + */ + static bool layerHasGeometry( const QgsVectorLayer *layer ); + + /** + * Returns true if the layer is the position tracking layer + */ + Q_INVOKABLE static bool isPositionTrackingLayer( QgsMapLayer *layer, QgsProject *project ); + + /** + * Returns true if the layer allows recording + */ + static bool recordingAllowed( QgsMapLayer *layer, QgsProject *project ); + + /** + * Returns QgsMapLayer pointer for given layer name and project. + * If layer with given name does not exist or there is no project, returns nullptr. + */ + static QgsMapLayer *mapLayerFromName( const QString &layerName, QgsProject *project ); + public slots: void onQgsLogMessageReceived( const QString &message, const QString &tag, Qgis::MessageLevel level ); diff --git a/app/layersproxymodel.cpp b/app/layersproxymodel.cpp index 6ad74f336..18a24aa8f 100644 --- a/app/layersproxymodel.cpp +++ b/app/layersproxymodel.cpp @@ -13,24 +13,13 @@ #include "qgsproject.h" #include "qgslayertree.h" -LayersProxyModel::LayersProxyModel( LayersModel *model, LayerModelTypes modelType ) : - mModelType( modelType ), - mModel( model ) +LayersProxyModel::LayersProxyModel( QObject *parent ) : + QgsMapLayerProxyModel{ parent } { - setSourceModel( mModel ); - - switch ( mModelType ) - { - case ActiveLayerSelection: - filterFunction = [this]( QgsMapLayer * layer ) { return recordingAllowed( layer ); }; - break; - default: - filterFunction = []( QgsMapLayer * ) { return true; }; - break; - } - QObject::connect( this, &LayersProxyModel::rowsInserted, this, &LayersProxyModel::countChanged ); QObject::connect( this, &LayersProxyModel::rowsRemoved, this, &LayersProxyModel::countChanged ); + + updateFilterFunction(); } bool LayersProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const @@ -45,33 +34,6 @@ bool LayersProxyModel::filterAcceptsRow( int source_row, const QModelIndex &sour return filterFunction( layer ); } -bool layerHasGeometry( const QgsVectorLayer *layer ) -{ - if ( !layer || !layer->isValid() ) - return false; - return layer->wkbType() != Qgis::WkbType::NoGeometry && layer->wkbType() != Qgis::WkbType::Unknown; -} - -bool LayersProxyModel::recordingAllowed( QgsMapLayer *layer ) const -{ - if ( !layer || !layer->isValid() ) - return false; - - QgsVectorLayer *vectorLayer = qobject_cast( layer ); - return ( vectorLayer && !vectorLayer->readOnly() && layerHasGeometry( vectorLayer ) && layerVisible( layer ) ); -} - -bool LayersProxyModel::layerVisible( QgsMapLayer *layer ) const -{ - QgsLayerTree *root = QgsProject::instance()->layerTreeRoot(); - QgsLayerTreeLayer *layerTree = root->findLayer( layer ); - - if ( layerTree ) - return layerTree->isVisible(); - - return false; -} - QList LayersProxyModel::layers() const { QList filteredLayers; @@ -153,3 +115,72 @@ QVariant LayersProxyModel::getData( QModelIndex index, int role ) const { return sourceModel()->data( index, role ); } + +QgsProject *LayersProxyModel::qgsProject() const +{ + return mProject; +} + +void LayersProxyModel::setQgsProject( QgsProject *project ) +{ + if ( mProject != project ) + { + mProject = project; + emit qgsProjectChanged(); + } +} + +LayersProxyModel::LayerModelTypes LayersProxyModel::modelType() const +{ + return mModelType; +} + +void LayersProxyModel::setModelType( LayerModelTypes type ) +{ + if ( mModelType != type ) + { + mModelType = type; + + updateFilterFunction(); + + emit modelTypeChanged(); + } +} + +LayersModel *LayersProxyModel::model() const +{ + return mModel; +} + +void LayersProxyModel::setModel( LayersModel *model ) +{ + if ( mModel != model ) + { + mModel = model; + setSourceModel( mModel ); + emit modelChanged(); + } +} + +void LayersProxyModel::updateFilterFunction() +{ + beginResetModel(); + + switch ( mModelType ) + { + case ActiveLayerSelection: + filterFunction = [this]( QgsMapLayer * layer ) + { + return InputUtils::recordingAllowed( layer, mProject ); + }; + break; + default: + filterFunction = []( QgsMapLayer * ) + { + return true; + }; + break; + } + + endResetModel(); +} diff --git a/app/layersproxymodel.h b/app/layersproxymodel.h index 13a5f66f3..caa3c2840 100644 --- a/app/layersproxymodel.h +++ b/app/layersproxymodel.h @@ -17,23 +17,28 @@ #include "qgsmaplayer.h" #include "qgsmaplayerproxymodel.h" #include "qgsvectorlayer.h" +#include "inpututils.h" #include "layersmodel.h" -enum LayerModelTypes -{ - ActiveLayerSelection, - AllLayers -}; - class LayersProxyModel : public QgsMapLayerProxyModel { Q_OBJECT Q_PROPERTY( int count READ rowCount NOTIFY countChanged ) + Q_PROPERTY( QgsProject *qgsProject READ qgsProject WRITE setQgsProject NOTIFY qgsProjectChanged ) + Q_PROPERTY( LayerModelTypes modelType READ modelType WRITE setModelType NOTIFY modelTypeChanged ) + Q_PROPERTY( LayersModel *model READ model WRITE setModel NOTIFY modelChanged ) public: - LayersProxyModel( LayersModel *model, LayerModelTypes modelType = LayerModelTypes::AllLayers ); + Q_INVOKABLE explicit LayersProxyModel( QObject *parent = nullptr ); + + enum LayerModelTypes + { + AllLayers, + ActiveLayerSelection + }; + Q_ENUM( LayerModelTypes ); bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override; @@ -54,20 +59,30 @@ class LayersProxyModel : public QgsMapLayerProxyModel */ QList layers() const; + //! Filter current model according to its type + void updateFilterFunction(); + + //! Getters and setters + + QgsProject *qgsProject() const; + void setQgsProject( QgsProject *project ); + + LayerModelTypes modelType() const; + void setModelType( LayerModelTypes type ); + + LayersModel *model() const; + void setModel( LayersModel *model ); + signals: void countChanged(); + void qgsProjectChanged(); + void modelTypeChanged(); + void modelChanged(); public slots: void refreshData(); private: - - //! returns if input layer is capable of recording new features - bool recordingAllowed( QgsMapLayer *layer ) const; - - //! filters if input layer is visible in current map theme - bool layerVisible( QgsMapLayer *layer ) const; - LayerModelTypes mModelType; LayersModel *mModel; @@ -78,6 +93,8 @@ class LayersProxyModel : public QgsMapLayerProxyModel * In future will allow dependency injection of custom filter functions. */ std::function filterFunction; + + QgsProject *mProject; }; #endif // LAYERSPROXYMODEL_H diff --git a/app/main.cpp b/app/main.cpp index 6f741af8f..b7911356c 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -260,8 +260,6 @@ void initDeclarative() qmlRegisterUncreatableType( "mm", 1, 0, "MerginServerType", "MerginServerType Enum" ); qmlRegisterUncreatableType( "mm", 1, 0, "MerginSubscriptionStatus", "MerginSubscriptionStatus Enum" ); qmlRegisterUncreatableType( "mm", 1, 0, "MerginProjectStatusModel", "Enum" ); - qmlRegisterUncreatableType( "mm", 1, 0, "LayersModel", "" ); - qmlRegisterUncreatableType( "mm", 1, 0, "LayersProxyModel", "" ); qmlRegisterUncreatableType( "mm", 1, 0, "ActiveLayer", "" ); qmlRegisterUncreatableType( "mm", 1, 0, "StreamingIntervalType", "StreamingIntervalType Enum" ); qmlRegisterUncreatableType( "mm", 1, 0, "RegistrationError", "RegistrationError Enum" ); @@ -350,6 +348,10 @@ void initDeclarative() qmlRegisterType< RecordingMapTool >( "mm", 1, 0, "RecordingMapTool" ); qmlRegisterType< SplittingMapTool >( "mm", 1, 0, "SplittingMapTool" ); qmlRegisterType< MeasurementMapTool >( "mm", 1, 0, "MeasurementMapTool" ); + + // layers model + qmlRegisterType( "mm", 1, 0, "LayersProxyModel" ); + qmlRegisterType( "mm", 1, 0, "LayersModel" ); } void addQmlImportPath( QQmlEngine &engine ) @@ -489,13 +491,8 @@ int main( int argc, char *argv[] ) ProjectWizard pw( projectDir ); NotificationModel notificationModel; - // layer models - LayersModel lm; - LayersProxyModel recordingLpm( &lm, LayerModelTypes::ActiveLayerSelection ); - ActiveLayer al; - ActiveProject activeProject( as, al, recordingLpm, localProjectsManager ); - + ActiveProject activeProject( as, al, localProjectsManager ); std::unique_ptr vm( new VariablesManager( ma.get() ) ); vm->registerInputExpressionFunctions(); @@ -679,7 +676,6 @@ int main( int argc, char *argv[] ) engine.rootContext()->setContextProperty( "__appSettings", &as ); engine.rootContext()->setContextProperty( "__merginApi", ma.get() ); engine.rootContext()->setContextProperty( "__merginProjectStatusModel", &mpsm ); - engine.rootContext()->setContextProperty( "__recordingLayersModel", &recordingLpm ); engine.rootContext()->setContextProperty( "__activeLayer", &al ); engine.rootContext()->setContextProperty( "__projectWizard", &pw ); engine.rootContext()->setContextProperty( "__localProjectsManager", &localProjectsManager ); diff --git a/app/qml/main.qml b/app/qml/main.qml index 4cb127ddb..b5d5f699d 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -274,11 +274,13 @@ ApplicationWindow { } MMToolbarButton { + id: addButton + text: qsTr("Add") iconSource: __style.addIcon visible: __activeProject.projectRole !== "reader" onClicked: { - if ( __recordingLayersModel.rowCount() > 0 ) { + if ( __activeProject.projectHasRecordingLayers() ) { stateManager.state = "map" map.record() } diff --git a/app/qml/map/MMMapController.qml b/app/qml/map/MMMapController.qml index 43f76d8af..be63cc52b 100644 --- a/app/qml/map/MMMapController.qml +++ b/app/qml/map/MMMapController.qml @@ -471,7 +471,10 @@ Item { text: __activeLayer.layerName leftIconSource: __inputUtils.loadIconFromLayer( __activeLayer.layer ) - onClicked: activeLayerPanel.open() + onClicked: { + activeLayerPanelLoader.active = true + activeLayerPanelLoader.item.open() + } } Item { @@ -795,39 +798,57 @@ Item { } } - MMListDrawer { - id: activeLayerPanel + Loader { + id: activeLayerPanelLoader + active: false + sourceComponent: activeLayerPanelComponent + } - drawerHeader.title: qsTr( "Choose Active Layer" ) + Component { + id: activeLayerPanelComponent + + MMListDrawer { + id: activeLayerPanel - list.model: __recordingLayersModel + drawerHeader.title: qsTr( "Choose Active Layer" ) - list.delegate: MMListDelegate { - text: model.layerName + onClosed: activeLayerPanelLoader.active = false - // TODO: why we need to set hight here? - height: __style.menuDrawerHeight + list.model: MM.LayersProxyModel { + id: recordingLayersModel - leftContent: MMIcon { - source: model.iconSource + qgsProject: __activeProject.qgsProject + modelType: MM.LayersProxyModel.ActiveLayerSelection + model: MM.LayersModel {} } - rightContent: MMIcon { - source: __style.doneCircleIcon - visible: __activeLayer.layerId === model.layerId - } + list.delegate: MMListDelegate { + text: model.layerName - onClicked: { - __activeProject.setActiveLayer( __recordingLayersModel.layerFromLayerId( model.layerId ) ) - activeLayerPanel.close() + // TODO: why we need to set hight here? + height: __style.menuDrawerHeight + + leftContent: MMIcon { + source: model.iconSource + } + + rightContent: MMIcon { + source: __style.doneCircleIcon + visible: __activeLayer.layerId === model.layerId + } + + onClicked: { + __activeProject.setActiveLayer( recordingLayersModel.layerFromLayerId( model.layerId ) ) + activeLayerPanel.close() + } } - } - emptyStateDelegate: MMMessage { - image: __style.negativeMMSymbolImage - description: qsTr( "Could not find any editable layers in the project." ) - linkText: qsTr( "See how to enable digitizing in your project." ) - link: __inputHelp.howToEnableDigitizingLink + emptyStateDelegate: MMMessage { + image: __style.negativeMMSymbolImage + description: qsTr( "Could not find any editable layers in the project." ) + linkText: qsTr( "See how to enable digitizing in your project." ) + link: __inputHelp.howToEnableDigitizingLink + } } } diff --git a/app/test/testactiveproject.cpp b/app/test/testactiveproject.cpp index 8b14abd24..d58d911ed 100644 --- a/app/test/testactiveproject.cpp +++ b/app/test/testactiveproject.cpp @@ -37,9 +37,7 @@ void TestActiveProject::testProjectValidations() AppSettings as; ActiveLayer al; - LayersModel lm; - LayersProxyModel lpm( &lm, LayerModelTypes::ActiveLayerSelection ); - ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager() ); + ActiveProject activeProject( as, al, mApi->localProjectsManager() ); QSignalSpy spyReportIssues( &activeProject, &ActiveProject::reportIssue ); QSignalSpy spyErrorsFound( &activeProject, &ActiveProject::loadingErrorFound ); @@ -64,9 +62,7 @@ void TestActiveProject::testProjectLoadFailure() AppSettings as; ActiveLayer al; - LayersModel lm; - LayersProxyModel lpm( &lm, LayerModelTypes::ActiveLayerSelection ); - ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager() ); + ActiveProject activeProject( as, al, mApi->localProjectsManager() ); mApi->localProjectsManager().addLocalProject( projectdir, projectname ); @@ -86,9 +82,7 @@ void TestActiveProject::testPositionTrackingFlag() AppSettings as; ActiveLayer al; - LayersModel lm; - LayersProxyModel lpm( &lm, LayerModelTypes::ActiveLayerSelection ); - ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager() ); + ActiveProject activeProject( as, al, mApi->localProjectsManager() ); // project "planes" - tracking not enabled QString projectDir = TestUtils::testDataDir() + "/planes/"; diff --git a/app/test/testmerginapi.cpp b/app/test/testmerginapi.cpp index a7ee5a4be..73b88e6f7 100644 --- a/app/test/testmerginapi.cpp +++ b/app/test/testmerginapi.cpp @@ -2368,8 +2368,7 @@ void TestMerginApi::testAutosync() InputUtils::cpDir( TestUtils::testDataDir() + "/planes", projectdir ); MapThemesModel mtm; AppSettings as; ActiveLayer al; - LayersModel lm; LayersProxyModel lpm( &lm, LayerModelTypes::ActiveLayerSelection ); - ActiveProject activeProject( as, al, lpm, mApi->localProjectsManager() ); + ActiveProject activeProject( as, al, mApi->localProjectsManager() ); mApi->localProjectsManager().addLocalProject( projectdir, projectname ); diff --git a/app/test/testutils.cpp b/app/test/testutils.cpp index 966e3cfb6..90f67ac5c 100644 --- a/app/test/testutils.cpp +++ b/app/test/testutils.cpp @@ -17,6 +17,11 @@ #include "inpututils.h" #include "merginapi.h" +#include "qgsvectorlayer.h" +#include "qgsproject.h" +#include "qgslayertree.h" +#include "qgslayertreelayer.h" + void TestUtils::merginGetAuthCredentials( MerginApi *api, QString &apiRoot, QString &username, QString &password ) { Q_ASSERT( api ); @@ -183,3 +188,139 @@ QgsProject *TestUtils::loadPlanesTestProject() return project; } + +void TestUtils::testLayerHasGeometry() +{ + // null layer => should be false + QCOMPARE( InputUtils::layerHasGeometry( nullptr ), false ); + + // invalid layer => should be false + QgsVectorLayer *invalidLayer = new QgsVectorLayer( "", "InvalidLayer", "none" ); + QVERIFY( invalidLayer->isValid() == false ); + QCOMPARE( InputUtils::layerHasGeometry( invalidLayer ), false ); + delete invalidLayer; + + // valid memory layer with geometry + QgsVectorLayer *pointLayer = new QgsVectorLayer( "Point?crs=EPSG:4326", "ValidPointLayer", "memory" ); + QVERIFY( pointLayer->isValid() ); + QCOMPARE( InputUtils::layerHasGeometry( pointLayer ), true ); + + // layer with NoGeo => should be false + QgsVectorLayer *noGeomLayer = new QgsVectorLayer( "None", "NoGeometryLayer", "memory" ); + QVERIFY( noGeomLayer->isValid() ); + QCOMPARE( InputUtils::layerHasGeometry( noGeomLayer ), false ); + + delete pointLayer; + delete noGeomLayer; +} + +void TestUtils::testLayerVisible() +{ + QgsProject *project = new QgsProject(); + project->clear(); + + // null layer => should be false + QCOMPARE( InputUtils::layerVisible( nullptr, project ), false ); + + // valid memory layer + QgsVectorLayer *layer = new QgsVectorLayer( "LineString?crs=EPSG:4326", "VisibleLineLayer", "memory" ); + QVERIFY( layer->isValid() ); + + // won't appear in the layer tree => false + QCOMPARE( InputUtils::layerVisible( layer, project ), false ); + + // added to project => true + project->addMapLayer( layer ); + QCOMPARE( InputUtils::layerVisible( layer, project ), true ); + + // hide layer => false + QgsLayerTree *root = project->layerTreeRoot(); + QgsLayerTreeLayer *layerTree = root->findLayer( layer ); + QVERIFY( layerTree ); + layerTree->setItemVisibilityChecked( false ); + QCOMPARE( InputUtils::layerVisible( layer, project ), false ); + + delete project; +} + +void TestUtils::testIsPositionTrackingLayer() +{ + QCOMPARE( InputUtils::isPositionTrackingLayer( nullptr, nullptr ), false ); + + QgsProject *project = new QgsProject(); + QgsVectorLayer *layer = new QgsVectorLayer( "Point?crs=EPSG:4326", "TrackingLayer", "memory" ); + project->addMapLayer( layer ); + QCOMPARE( InputUtils::isPositionTrackingLayer( layer, project ), false ); + + // tracking layer ID => true + QString layerId = layer->id(); + project->writeEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PositionTracking/TrackingLayer" ), layerId ); + QCOMPARE( InputUtils::isPositionTrackingLayer( layer, project ), true ); + + // not tracking layer ID => false + project->writeEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PositionTracking/TrackingLayer" ), QString( "some-other-id" ) ); + QCOMPARE( InputUtils::isPositionTrackingLayer( layer, project ), false ); + + delete project; +} + +void TestUtils::testRecordingAllowed() +{ + QCOMPARE( InputUtils::recordingAllowed( nullptr, nullptr ), false ); + + QgsProject *project = new QgsProject(); + + //valid vector layer => true + QgsVectorLayer *validLayer = new QgsVectorLayer( "Polygon?crs=EPSG:4326", "PolygonLayer", "memory" ); + project->addMapLayer( validLayer ); + QCOMPARE( InputUtils::recordingAllowed( validLayer, project ), true ); + + // not visible => false + QgsLayerTreeLayer *layerNode = project->layerTreeRoot()->findLayer( validLayer ); + QVERIFY( layerNode ); + layerNode->setItemVisibilityChecked( false ); + QCOMPARE( InputUtils::recordingAllowed( validLayer, project ), false ); + layerNode->setItemVisibilityChecked( true ); // restore + + // read-only layer => false + validLayer->setReadOnly( true ); + QCOMPARE( InputUtils::recordingAllowed( validLayer, project ), false ); + validLayer->setReadOnly( false ); // restore + + // noGeo => false + QgsVectorLayer *noGeomLayer = new QgsVectorLayer( "None", "NoGeomLayer", "memory" ); + project->addMapLayer( noGeomLayer ); + QCOMPARE( InputUtils::recordingAllowed( noGeomLayer, project ), false ); + + // position tracking layer => false + project->writeEntry( "Mergin", "PositionTracking/TrackingLayer", validLayer->id() ); + QCOMPARE( InputUtils::recordingAllowed( validLayer, project ), false ); + + // restore valid layer => true + project->writeEntry( "Mergin", "PositionTracking/TrackingLayer", QString() ); + QCOMPARE( InputUtils::recordingAllowed( validLayer, project ), true ); + + delete project; +} + +void TestUtils::testMapLayerFromName() +{ + QCOMPARE( InputUtils::mapLayerFromName( "Anything", nullptr ), static_cast( nullptr ) ); + + // empty layerName => nullptr + QgsProject *project = new QgsProject(); + QCOMPARE( InputUtils::mapLayerFromName( "", project ), static_cast( nullptr ) ); + + // added a named layer to project and check => should succeed + QgsVectorLayer *layer = new QgsVectorLayer( "Point?crs=EPSG:4326", "MyTestLayer", "memory" ); + QVERIFY( layer->isValid() ); + project->addMapLayer( layer ); + QgsMapLayer *found = InputUtils::mapLayerFromName( "MyTestLayer", project ); + QVERIFY( found != nullptr ); + QCOMPARE( found->name(), QString( "MyTestLayer" ) ); + + // non-existing name => nullptr + QCOMPARE( InputUtils::mapLayerFromName( "NoSuchName", project ), static_cast( nullptr ) ); + + delete project; +} diff --git a/app/test/testutils.h b/app/test/testutils.h index 9e6e7b682..65ccb4ec8 100644 --- a/app/test/testutils.h +++ b/app/test/testutils.h @@ -56,6 +56,12 @@ namespace TestUtils * Returns true if files were successfully created */ bool generateProjectFolder( const QString &rootPath, const QJsonDocument &structure ); + + void testLayerHasGeometry(); + void testLayerVisible(); + void testIsPositionTrackingLayer(); + void testRecordingAllowed(); + void testMapLayerFromName(); } #define COMPARENEAR(actual, expected, epsilon) \