Skip to content

Commit f70acf2

Browse files
authoredApr 30, 2019
Merge pull request #9901 from nyalldawson/invalid_qlr
Allow loading QLR files with invalid sources
2 parents fa60a7e + 78ccd41 commit f70acf2

File tree

4 files changed

+309
-13
lines changed

4 files changed

+309
-13
lines changed
 

‎src/app/qgsapplayertreeviewmenuprovider.cpp

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,18 +140,24 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
140140

141141
if ( layer && layer->isSpatial() )
142142
{
143-
menu->addAction( actions->actionZoomToLayer( mCanvas, menu ) );
143+
QAction *zoomToLayer = actions->actionZoomToLayer( mCanvas, menu );
144+
zoomToLayer->setEnabled( layer->isValid() );
145+
menu->addAction( zoomToLayer );
144146
if ( vlayer )
145147
{
146148
QAction *actionZoomSelected = actions->actionZoomToSelection( mCanvas, menu );
147-
actionZoomSelected->setEnabled( !vlayer->selectedFeatureIds().isEmpty() );
149+
actionZoomSelected->setEnabled( vlayer->isValid() && !vlayer->selectedFeatureIds().isEmpty() );
148150
menu->addAction( actionZoomSelected );
149151
}
150152
menu->addAction( actions->actionShowInOverview( menu ) );
151153
}
152154

153155
if ( vlayer )
154-
menu->addAction( actions->actionShowFeatureCount( menu ) );
156+
{
157+
QAction *showFeatureCount = actions->actionShowFeatureCount( menu );
158+
menu->addAction( showFeatureCount );
159+
showFeatureCount->setEnabled( vlayer->isValid() );
160+
}
155161

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

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

166173
if ( rlayer->rasterType() != QgsRasterLayer::Palette )
167-
menu->addAction( tr( "&Stretch Using Current Extent" ), QgisApp::instance(), &QgisApp::legendLayerStretchUsingCurrentExtent );
174+
{
175+
QAction *stretch = menu->addAction( tr( "&Stretch Using Current Extent" ), QgisApp::instance(), &QgisApp::legendLayerStretchUsingCurrentExtent );
176+
stretch->setEnabled( rlayer->isValid() );
177+
}
168178
}
169179

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

214225
// allow editing
215226
unsigned int cap = vlayer->dataProvider()->capabilities();
@@ -219,7 +230,7 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
219230
{
220231
menu->addAction( toggleEditingAction );
221232
toggleEditingAction->setChecked( vlayer->isEditable() );
222-
toggleEditingAction->setEnabled( true );
233+
toggleEditingAction->setEnabled( vlayer->isValid() );
223234
}
224235
if ( saveLayerEditsAction && vlayer->isModified() )
225236
{
@@ -298,10 +309,11 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
298309
QMenu *menuExportVector = new QMenu( tr( "Export" ), menu );
299310
QAction *actionSaveAs = new QAction( tr( "Save Features As…" ), menuExportVector );
300311
connect( actionSaveAs, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveAsFile(); } );
312+
actionSaveAs->setEnabled( vlayer->isValid() );
301313
menuExportVector->addAction( actionSaveAs );
302314
QAction *actionSaveSelectedFeaturesAs = new QAction( tr( "Save Selected Features As…" ), menuExportVector );
303315
connect( actionSaveSelectedFeaturesAs, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveAsFile( nullptr, true ); } );
304-
actionSaveSelectedFeaturesAs->setEnabled( vlayer->selectedFeatureCount() > 0 );
316+
actionSaveSelectedFeaturesAs->setEnabled( vlayer->isValid() && vlayer->selectedFeatureCount() > 0 );
305317
menuExportVector->addAction( actionSaveSelectedFeaturesAs );
306318
QAction *actionSaveAsDefinitionLayer = new QAction( tr( "Save as Layer Definition File…" ), menuExportVector );
307319
connect( actionSaveAsDefinitionLayer, &QAction::triggered, QgisApp::instance(), &QgisApp::saveAsLayerDefinition );
@@ -322,6 +334,7 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
322334
QAction *actionSaveStyle = new QAction( tr( "Save as QGIS Layer Style File…" ), menuExportRaster );
323335
connect( actionSaveAs, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveAsFile(); } );
324336
menuExportRaster->addAction( actionSaveAs );
337+
actionSaveAs->setEnabled( rlayer->isValid() );
325338
connect( actionSaveAsDefinitionLayer, &QAction::triggered, QgisApp::instance(), &QgisApp::saveAsLayerDefinition );
326339
menuExportRaster->addAction( actionSaveAsDefinitionLayer );
327340
connect( actionSaveStyle, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveStyleFile(); } );

‎src/core/qgslayerdefinition.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,10 @@ QList<QgsMapLayer *> QgsLayerDefinition::loadLayerDefinitionLayers( QDomDocument
280280
if ( !layer )
281281
continue;
282282

283-
if ( layer->readLayerXml( layerElem, context ) )
284-
{
285-
layers << layer;
286-
}
283+
// always add the layer, even if the source is invalid -- this allows users to fix the source
284+
// at a later stage and still retain all the layer properties intact
285+
layer->readLayerXml( layerElem, context );
286+
layers << layer;
287287
}
288288
return layers;
289289
}

‎tests/src/python/test_qgslayerdefinition.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,21 @@ def testVectorAndRaster(self):
110110
self.assertEqual(len(layers), 2)
111111
QgsProject.instance().removeAllMapLayers()
112112

113+
def testInvalidSource(self):
114+
# Load a QLR containing a vector layer with a broken path
115+
QgsProject.instance().removeAllMapLayers()
116+
layers = QgsProject.instance().mapLayers()
117+
self.assertEqual(len(layers), 0)
118+
119+
(result, errMsg) = QgsLayerDefinition.loadLayerDefinition(TEST_DATA_DIR + '/invalid_source.qlr', QgsProject.instance(), QgsProject.instance().layerTreeRoot())
120+
self.assertTrue(result)
121+
self.assertFalse(errMsg)
122+
123+
layers = QgsProject.instance().mapLayers()
124+
self.assertEqual(len(layers), 1)
125+
self.assertFalse(list(layers.values())[0].isValid())
126+
QgsProject.instance().removeAllMapLayers()
127+
113128

114129
if __name__ == '__main__':
115130
unittest.main()

‎tests/testdata/invalid_source.qlr

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
<!DOCTYPE qgis-layer-definition>
2+
<qlr>
3+
<layer-tree-group expanded="1" name="" checked="Qt::Checked">
4+
<customproperties/>
5+
<layer-tree-layer source="C:\temp/points.shp" expanded="1" id="points_1cff66ba_ca06_4c6d_8542_610e215f65ca" name="points" providerKey="ogr" checked="Qt::Checked">
6+
<customproperties/>
7+
</layer-tree-layer>
8+
</layer-tree-group>
9+
<maplayers>
10+
<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">
11+
<id>points_1cff66ba_ca06_4c6d_8542_610e215f65ca</id>
12+
<datasource>C:\temp/points.shp</datasource>
13+
<keywordList>
14+
<value></value>
15+
</keywordList>
16+
<layername>points</layername>
17+
<srs>
18+
<spatialrefsys>
19+
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
20+
<srsid>3452</srsid>
21+
<srid>4326</srid>
22+
<authid>EPSG:4326</authid>
23+
<description>WGS 84</description>
24+
<projectionacronym>longlat</projectionacronym>
25+
<ellipsoidacronym>WGS84</ellipsoidacronym>
26+
<geographicflag>true</geographicflag>
27+
</spatialrefsys>
28+
</srs>
29+
<resourceMetadata>
30+
<identifier></identifier>
31+
<parentidentifier></parentidentifier>
32+
<language></language>
33+
<type></type>
34+
<title></title>
35+
<abstract></abstract>
36+
<links/>
37+
<fees></fees>
38+
<encoding></encoding>
39+
<crs>
40+
<spatialrefsys>
41+
<proj4></proj4>
42+
<srsid>0</srsid>
43+
<srid>0</srid>
44+
<authid></authid>
45+
<description></description>
46+
<projectionacronym></projectionacronym>
47+
<ellipsoidacronym></ellipsoidacronym>
48+
<geographicflag>false</geographicflag>
49+
</spatialrefsys>
50+
</crs>
51+
<extent/>
52+
</resourceMetadata>
53+
<provider encoding="UTF-8">ogr</provider>
54+
<vectorjoins/>
55+
<layerDependencies/>
56+
<dataDependencies/>
57+
<legend type="default-vector"/>
58+
<expressionfields/>
59+
<map-layer-style-manager current="default">
60+
<map-layer-style name="default"/>
61+
</map-layer-style-manager>
62+
<auxiliaryLayer/>
63+
<flags>
64+
<Identifiable>1</Identifiable>
65+
<Removable>1</Removable>
66+
<Searchable>1</Searchable>
67+
</flags>
68+
<renderer-v2 symbollevels="0" graduatedMethod="GraduatedColor" attr="Importance" enableorderby="0" type="graduatedSymbol" forceraster="0">
69+
<ranges>
70+
<range render="true" upper="1.000000000000000" symbol="0" label="1.000" lower="1.000000000000000"/>
71+
<range render="true" upper="3.000000000000000" symbol="1" label="1.001 - 3.000" lower="1.001000000000000"/>
72+
<range render="true" upper="4.000000000000000" symbol="2" label="3.001 - 4.000" lower="3.001000000000000"/>
73+
<range render="true" upper="10.000000000000000" symbol="3" label="4.001 - 10.000" lower="4.001000000000000"/>
74+
<range render="true" upper="20.000000000000000" symbol="4" label="10.001 - 20.000" lower="10.000999999999999"/>
75+
</ranges>
76+
<symbols>
77+
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="0">
78+
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
79+
<prop k="angle" v="0"/>
80+
<prop k="color" v="255,255,127,255"/>
81+
<prop k="horizontal_anchor_point" v="1"/>
82+
<prop k="joinstyle" v="bevel"/>
83+
<prop k="name" v="circle"/>
84+
<prop k="offset" v="0,0"/>
85+
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
86+
<prop k="offset_unit" v="Point"/>
87+
<prop k="outline_color" v="0,0,0,255"/>
88+
<prop k="outline_style" v="solid"/>
89+
<prop k="outline_width" v="0.5"/>
90+
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
91+
<prop k="outline_width_unit" v="Point"/>
92+
<prop k="scale_method" v="diameter"/>
93+
<prop k="size" v="4"/>
94+
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
95+
<prop k="size_unit" v="Point"/>
96+
<prop k="vertical_anchor_point" v="1"/>
97+
<data_defined_properties>
98+
<Option type="Map">
99+
<Option value="" type="QString" name="name"/>
100+
<Option name="properties"/>
101+
<Option value="collection" type="QString" name="type"/>
102+
</Option>
103+
</data_defined_properties>
104+
</layer>
105+
</symbol>
106+
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="1">
107+
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
108+
<prop k="angle" v="0"/>
109+
<prop k="color" v="250,209,85,255"/>
110+
<prop k="horizontal_anchor_point" v="1"/>
111+
<prop k="joinstyle" v="bevel"/>
112+
<prop k="name" v="circle"/>
113+
<prop k="offset" v="0,0"/>
114+
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
115+
<prop k="offset_unit" v="Point"/>
116+
<prop k="outline_color" v="0,0,0,255"/>
117+
<prop k="outline_style" v="solid"/>
118+
<prop k="outline_width" v="0.5"/>
119+
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
120+
<prop k="outline_width_unit" v="Point"/>
121+
<prop k="scale_method" v="diameter"/>
122+
<prop k="size" v="4"/>
123+
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
124+
<prop k="size_unit" v="Point"/>
125+
<prop k="vertical_anchor_point" v="1"/>
126+
<data_defined_properties>
127+
<Option type="Map">
128+
<Option value="" type="QString" name="name"/>
129+
<Option name="properties"/>
130+
<Option value="collection" type="QString" name="type"/>
131+
</Option>
132+
</data_defined_properties>
133+
</layer>
134+
</symbol>
135+
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="2">
136+
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
137+
<prop k="angle" v="0"/>
138+
<prop k="color" v="242,167,46,255"/>
139+
<prop k="horizontal_anchor_point" v="1"/>
140+
<prop k="joinstyle" v="bevel"/>
141+
<prop k="name" v="circle"/>
142+
<prop k="offset" v="0,0"/>
143+
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
144+
<prop k="offset_unit" v="Point"/>
145+
<prop k="outline_color" v="0,0,0,255"/>
146+
<prop k="outline_style" v="solid"/>
147+
<prop k="outline_width" v="0.5"/>
148+
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
149+
<prop k="outline_width_unit" v="Point"/>
150+
<prop k="scale_method" v="diameter"/>
151+
<prop k="size" v="4"/>
152+
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
153+
<prop k="size_unit" v="Point"/>
154+
<prop k="vertical_anchor_point" v="1"/>
155+
<data_defined_properties>
156+
<Option type="Map">
157+
<Option value="" type="QString" name="name"/>
158+
<Option name="properties"/>
159+
<Option value="collection" type="QString" name="type"/>
160+
</Option>
161+
</data_defined_properties>
162+
</layer>
163+
</symbol>
164+
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="3">
165+
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
166+
<prop k="angle" v="0"/>
167+
<prop k="color" v="173,83,19,255"/>
168+
<prop k="horizontal_anchor_point" v="1"/>
169+
<prop k="joinstyle" v="bevel"/>
170+
<prop k="name" v="circle"/>
171+
<prop k="offset" v="0,0"/>
172+
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
173+
<prop k="offset_unit" v="Point"/>
174+
<prop k="outline_color" v="0,0,0,255"/>
175+
<prop k="outline_style" v="solid"/>
176+
<prop k="outline_width" v="0.5"/>
177+
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
178+
<prop k="outline_width_unit" v="Point"/>
179+
<prop k="scale_method" v="diameter"/>
180+
<prop k="size" v="4"/>
181+
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
182+
<prop k="size_unit" v="Point"/>
183+
<prop k="vertical_anchor_point" v="1"/>
184+
<data_defined_properties>
185+
<Option type="Map">
186+
<Option value="" type="QString" name="name"/>
187+
<Option name="properties"/>
188+
<Option value="collection" type="QString" name="type"/>
189+
</Option>
190+
</data_defined_properties>
191+
</layer>
192+
</symbol>
193+
<symbol alpha="1" force_rhr="0" clip_to_extent="1" type="marker" name="4">
194+
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
195+
<prop k="angle" v="0"/>
196+
<prop k="color" v="107,0,0,255"/>
197+
<prop k="horizontal_anchor_point" v="1"/>
198+
<prop k="joinstyle" v="bevel"/>
199+
<prop k="name" v="circle"/>
200+
<prop k="offset" v="0,0"/>
201+
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
202+
<prop k="offset_unit" v="Point"/>
203+
<prop k="outline_color" v="0,0,0,255"/>
204+
<prop k="outline_style" v="solid"/>
205+
<prop k="outline_width" v="0.5"/>
206+
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
207+
<prop k="outline_width_unit" v="Point"/>
208+
<prop k="scale_method" v="diameter"/>
209+
<prop k="size" v="4"/>
210+
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
211+
<prop k="size_unit" v="Point"/>
212+
<prop k="vertical_anchor_point" v="1"/>
213+
<data_defined_properties>
214+
<Option type="Map">
215+
<Option value="" type="QString" name="name"/>
216+
<Option name="properties"/>
217+
<Option value="collection" type="QString" name="type"/>
218+
</Option>
219+
</data_defined_properties>
220+
</layer>
221+
</symbol>
222+
</symbols>
223+
<symmetricMode enabled="false" symmetryPoint="0" astride="false"/>
224+
<rotation/>
225+
<sizescale/>
226+
<labelformat trimtrailingzeroes="false" decimalplaces="4" format="%1 - %2"/>
227+
</renderer-v2>
228+
<customproperties/>
229+
<blendMode>0</blendMode>
230+
<featureBlendMode>0</featureBlendMode>
231+
<layerOpacity>1</layerOpacity>
232+
<geometryOptions removeDuplicateNodes="0" geometryPrecision="0">
233+
<activeChecks type="StringList">
234+
<Option value="" type="QString"/>
235+
</activeChecks>
236+
<checkConfiguration/>
237+
</geometryOptions>
238+
<fieldConfiguration/>
239+
<aliases/>
240+
<excludeAttributesWMS/>
241+
<excludeAttributesWFS/>
242+
<defaults/>
243+
<constraints/>
244+
<constraintExpressions/>
245+
<expressionfields/>
246+
<attributeactions/>
247+
<attributetableconfig sortExpression="" actionWidgetStyle="dropDown" sortOrder="0">
248+
<columns/>
249+
</attributetableconfig>
250+
<conditionalstyles>
251+
<rowstyles/>
252+
<fieldstyles/>
253+
</conditionalstyles>
254+
<editform tolerant="1"></editform>
255+
<editforminit/>
256+
<editforminitcodesource>0</editforminitcodesource>
257+
<editforminitfilepath></editforminitfilepath>
258+
<editforminitcode><![CDATA[]]></editforminitcode>
259+
<featformsuppress>0</featformsuppress>
260+
<editorlayout>generatedlayout</editorlayout>
261+
<editable/>
262+
<labelOnTop/>
263+
<widgets/>
264+
<previewExpression></previewExpression>
265+
<mapTip></mapTip>
266+
</maplayer>
267+
</maplayers>
268+
</qlr>

0 commit comments

Comments
 (0)
Please sign in to comment.