Skip to content

Commit

Permalink
Merge pull request #9901 from nyalldawson/invalid_qlr
Browse files Browse the repository at this point in the history
 Allow loading QLR files with invalid sources
  • Loading branch information
elpaso committed Apr 30, 2019
2 parents fa60a7e + 78ccd41 commit f70acf2
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 13 deletions.
31 changes: 22 additions & 9 deletions src/app/qgsapplayertreeviewmenuprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,24 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()

if ( layer && layer->isSpatial() )
{
menu->addAction( actions->actionZoomToLayer( mCanvas, menu ) );
QAction *zoomToLayer = actions->actionZoomToLayer( mCanvas, menu );
zoomToLayer->setEnabled( layer->isValid() );
menu->addAction( zoomToLayer );
if ( vlayer )
{
QAction *actionZoomSelected = actions->actionZoomToSelection( mCanvas, menu );
actionZoomSelected->setEnabled( !vlayer->selectedFeatureIds().isEmpty() );
actionZoomSelected->setEnabled( vlayer->isValid() && !vlayer->selectedFeatureIds().isEmpty() );
menu->addAction( actionZoomSelected );
}
menu->addAction( actions->actionShowInOverview( menu ) );
}

if ( vlayer )
menu->addAction( actions->actionShowFeatureCount( menu ) );
{
QAction *showFeatureCount = actions->actionShowFeatureCount( menu );
menu->addAction( showFeatureCount );
showFeatureCount->setEnabled( vlayer->isValid() );
}

QAction *actionCopyLayer = new QAction( tr( "Copy Layer" ), menu );
connect( actionCopyLayer, &QAction::triggered, QgisApp::instance(), &QgisApp::copyLayer );
Expand All @@ -161,10 +167,14 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()

if ( rlayer )
{
menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomActual.svg" ) ), tr( "&Zoom to Native Resolution (100%)" ), QgisApp::instance(), &QgisApp::legendLayerZoomNative );
QAction *zoomToNative = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomActual.svg" ) ), tr( "&Zoom to Native Resolution (100%)" ), QgisApp::instance(), &QgisApp::legendLayerZoomNative );
zoomToNative->setEnabled( rlayer->isValid() );

if ( rlayer->rasterType() != QgsRasterLayer::Palette )
menu->addAction( tr( "&Stretch Using Current Extent" ), QgisApp::instance(), &QgisApp::legendLayerStretchUsingCurrentExtent );
{
QAction *stretch = menu->addAction( tr( "&Stretch Using Current Extent" ), QgisApp::instance(), &QgisApp::legendLayerStretchUsingCurrentExtent );
stretch->setEnabled( rlayer->isValid() );
}
}

addCustomLayerActions( menu, layer );
Expand Down Expand Up @@ -208,8 +218,9 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
// attribute table
QgsSettings settings;
QgsAttributeTableFilterModel::FilterMode initialMode = settings.enumValue( QStringLiteral( "qgis/attributeTableBehavior" ), QgsAttributeTableFilterModel::ShowAll );
menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) ), tr( "&Open Attribute Table" ),
QgisApp::instance(), [ = ] { QgisApp::instance()->attributeTable( initialMode ); } );
QAction *attributeTable = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) ), tr( "&Open Attribute Table" ),
QgisApp::instance(), [ = ] { QgisApp::instance()->attributeTable( initialMode ); } );
attributeTable->setEnabled( vlayer->isValid() );

// allow editing
unsigned int cap = vlayer->dataProvider()->capabilities();
Expand All @@ -219,7 +230,7 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
{
menu->addAction( toggleEditingAction );
toggleEditingAction->setChecked( vlayer->isEditable() );
toggleEditingAction->setEnabled( true );
toggleEditingAction->setEnabled( vlayer->isValid() );
}
if ( saveLayerEditsAction && vlayer->isModified() )
{
Expand Down Expand Up @@ -298,10 +309,11 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
QMenu *menuExportVector = new QMenu( tr( "Export" ), menu );
QAction *actionSaveAs = new QAction( tr( "Save Features As…" ), menuExportVector );
connect( actionSaveAs, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveAsFile(); } );
actionSaveAs->setEnabled( vlayer->isValid() );
menuExportVector->addAction( actionSaveAs );
QAction *actionSaveSelectedFeaturesAs = new QAction( tr( "Save Selected Features As…" ), menuExportVector );
connect( actionSaveSelectedFeaturesAs, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveAsFile( nullptr, true ); } );
actionSaveSelectedFeaturesAs->setEnabled( vlayer->selectedFeatureCount() > 0 );
actionSaveSelectedFeaturesAs->setEnabled( vlayer->isValid() && vlayer->selectedFeatureCount() > 0 );
menuExportVector->addAction( actionSaveSelectedFeaturesAs );
QAction *actionSaveAsDefinitionLayer = new QAction( tr( "Save as Layer Definition File…" ), menuExportVector );
connect( actionSaveAsDefinitionLayer, &QAction::triggered, QgisApp::instance(), &QgisApp::saveAsLayerDefinition );
Expand All @@ -322,6 +334,7 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
QAction *actionSaveStyle = new QAction( tr( "Save as QGIS Layer Style File…" ), menuExportRaster );
connect( actionSaveAs, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveAsFile(); } );
menuExportRaster->addAction( actionSaveAs );
actionSaveAs->setEnabled( rlayer->isValid() );
connect( actionSaveAsDefinitionLayer, &QAction::triggered, QgisApp::instance(), &QgisApp::saveAsLayerDefinition );
menuExportRaster->addAction( actionSaveAsDefinitionLayer );
connect( actionSaveStyle, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveStyleFile(); } );
Expand Down
8 changes: 4 additions & 4 deletions src/core/qgslayerdefinition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,10 @@ QList<QgsMapLayer *> QgsLayerDefinition::loadLayerDefinitionLayers( QDomDocument
if ( !layer )
continue;

if ( layer->readLayerXml( layerElem, context ) )
{
layers << layer;
}
// always add the layer, even if the source is invalid -- this allows users to fix the source
// at a later stage and still retain all the layer properties intact
layer->readLayerXml( layerElem, context );
layers << layer;
}
return layers;
}
Expand Down
15 changes: 15 additions & 0 deletions tests/src/python/test_qgslayerdefinition.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ def testVectorAndRaster(self):
self.assertEqual(len(layers), 2)
QgsProject.instance().removeAllMapLayers()

def testInvalidSource(self):
# Load a QLR containing a vector layer with a broken path
QgsProject.instance().removeAllMapLayers()
layers = QgsProject.instance().mapLayers()
self.assertEqual(len(layers), 0)

(result, errMsg) = QgsLayerDefinition.loadLayerDefinition(TEST_DATA_DIR + '/invalid_source.qlr', QgsProject.instance(), QgsProject.instance().layerTreeRoot())
self.assertTrue(result)
self.assertFalse(errMsg)

layers = QgsProject.instance().mapLayers()
self.assertEqual(len(layers), 1)
self.assertFalse(list(layers.values())[0].isValid())
QgsProject.instance().removeAllMapLayers()


if __name__ == '__main__':
unittest.main()
268 changes: 268 additions & 0 deletions tests/testdata/invalid_source.qlr
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
<!DOCTYPE qgis-layer-definition>
<qlr>
<layer-tree-group expanded="1" name="" checked="Qt::Checked">
<customproperties/>
<layer-tree-layer source="C:\temp/points.shp" expanded="1" id="points_1cff66ba_ca06_4c6d_8542_610e215f65ca" name="points" providerKey="ogr" checked="Qt::Checked">
<customproperties/>
</layer-tree-layer>
</layer-tree-group>
<maplayers>
<maplayer geometry="Point" labelsEnabled="0" autoRefreshEnabled="0" styleCategories="AllStyleCategories" refreshOnNotifyEnabled="0" maxScale="0" refreshOnNotifyMessage="" simplifyLocal="1" wkbType="Point" simplifyDrawingTol="1" autoRefreshTime="0" simplifyMaxScale="1" readOnly="0" minScale="0" simplifyDrawingHints="1" simplifyAlgorithm="0" type="vector" hasScaleBasedVisibilityFlag="0">
<id>points_1cff66ba_ca06_4c6d_8542_610e215f65ca</id>
<datasource>C:\temp/points.shp</datasource>
<keywordList>
<value></value>
</keywordList>
<layername>points</layername>
<srs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</srs>
<resourceMetadata>
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type></type>
<title></title>
<abstract></abstract>
<links/>
<fees></fees>
<encoding></encoding>
<crs>
<spatialrefsys>
<proj4></proj4>
<srsid>0</srsid>
<srid>0</srid>
<authid></authid>
<description></description>
<projectionacronym></projectionacronym>
<ellipsoidacronym></ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</crs>
<extent/>
</resourceMetadata>
<provider encoding="UTF-8">ogr</provider>
<vectorjoins/>
<layerDependencies/>
<dataDependencies/>
<legend type="default-vector"/>
<expressionfields/>
<map-layer-style-manager current="default">
<map-layer-style name="default"/>
</map-layer-style-manager>
<auxiliaryLayer/>
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<renderer-v2 symbollevels="0" graduatedMethod="GraduatedColor" attr="Importance" enableorderby="0" type="graduatedSymbol" forceraster="0">
<ranges>
<range render="true" upper="1.000000000000000" symbol="0" label="1.000" lower="1.000000000000000"/>
<range render="true" upper="3.000000000000000" symbol="1" label="1.001 - 3.000" lower="1.001000000000000"/>
<range render="true" upper="4.000000000000000" symbol="2" label="3.001 - 4.000" lower="3.001000000000000"/>
<range render="true" upper="10.000000000000000" symbol="3" label="4.001 - 10.000" lower="4.001000000000000"/>
<range render="true" upper="20.000000000000000" symbol="4" label="10.001 - 20.000" lower="10.000999999999999"/>
</ranges>
<symbols>
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="0">
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
<prop k="angle" v="0"/>
<prop k="color" v="255,255,127,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="Point"/>
<prop k="outline_color" v="0,0,0,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0.5"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="Point"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="4"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="Point"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option value="" type="QString" name="name"/>
<Option name="properties"/>
<Option value="collection" type="QString" name="type"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="1">
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
<prop k="angle" v="0"/>
<prop k="color" v="250,209,85,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="Point"/>
<prop k="outline_color" v="0,0,0,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0.5"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="Point"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="4"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="Point"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option value="" type="QString" name="name"/>
<Option name="properties"/>
<Option value="collection" type="QString" name="type"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="2">
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
<prop k="angle" v="0"/>
<prop k="color" v="242,167,46,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="Point"/>
<prop k="outline_color" v="0,0,0,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0.5"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="Point"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="4"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="Point"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option value="" type="QString" name="name"/>
<Option name="properties"/>
<Option value="collection" type="QString" name="type"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="3">
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
<prop k="angle" v="0"/>
<prop k="color" v="173,83,19,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="Point"/>
<prop k="outline_color" v="0,0,0,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0.5"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="Point"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="4"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="Point"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option value="" type="QString" name="name"/>
<Option name="properties"/>
<Option value="collection" type="QString" name="type"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="4">
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
<prop k="angle" v="0"/>
<prop k="color" v="107,0,0,255"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="Point"/>
<prop k="outline_color" v="0,0,0,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0.5"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="Point"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="4"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="Point"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option value="" type="QString" name="name"/>
<Option name="properties"/>
<Option value="collection" type="QString" name="type"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<symmetricMode enabled="false" symmetryPoint="0" astride="false"/>
<rotation/>
<sizescale/>
<labelformat trimtrailingzeroes="false" decimalplaces="4" format="%1 - %2"/>
</renderer-v2>
<customproperties/>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<geometryOptions removeDuplicateNodes="0" geometryPrecision="0">
<activeChecks type="StringList">
<Option value="" type="QString"/>
</activeChecks>
<checkConfiguration/>
</geometryOptions>
<fieldConfiguration/>
<aliases/>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<defaults/>
<constraints/>
<constraintExpressions/>
<expressionfields/>
<attributeactions/>
<attributetableconfig sortExpression="" actionWidgetStyle="dropDown" sortOrder="0">
<columns/>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<editform tolerant="1"></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable/>
<labelOnTop/>
<widgets/>
<previewExpression></previewExpression>
<mapTip></mapTip>
</maplayer>
</maplayers>
</qlr>

0 comments on commit f70acf2

Please sign in to comment.