Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgslayoutitemmapoverview.cpp
3 : : --------------------
4 : : begin : October 2017
5 : : copyright : (C) 2017 by Nyall Dawson
6 : : email : nyall dot dawson at gmail dot com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgslayoutitemmapoverview.h"
19 : : #include "qgslayoutitemmap.h"
20 : : #include "qgslayout.h"
21 : : #include "qgssymbollayerutils.h"
22 : : #include "qgssymbol.h"
23 : : #include "qgsmapsettings.h"
24 : : #include "qgspainting.h"
25 : : #include "qgspathresolver.h"
26 : : #include "qgsreadwritecontext.h"
27 : : #include "qgslayoututils.h"
28 : : #include "qgsexception.h"
29 : : #include "qgsvectorlayer.h"
30 : : #include "qgssinglesymbolrenderer.h"
31 : : #include "qgsstyleentityvisitor.h"
32 : :
33 : : #include <QPainter>
34 : :
35 : 0 : QgsLayoutItemMapOverview::QgsLayoutItemMapOverview( const QString &name, QgsLayoutItemMap *map )
36 : 0 : : QgsLayoutItemMapItem( name, map )
37 : 0 : , mExtentLayer( std::make_unique< QgsVectorLayer >( QStringLiteral( "Polygon?crs=EPSG:4326" ), tr( "Overview" ), QStringLiteral( "memory" ), QgsVectorLayer::LayerOptions( map && map->layout() && map->layout()->project() ? map->layout()->project()->transformContext() : QgsCoordinateTransformContext() ) ) )
38 : 0 : {
39 : 0 : createDefaultFrameSymbol();
40 : 0 : }
41 : :
42 : 0 : QgsLayoutItemMapOverview::~QgsLayoutItemMapOverview() = default;
43 : :
44 : 0 : void QgsLayoutItemMapOverview::createDefaultFrameSymbol()
45 : : {
46 : 0 : QVariantMap properties;
47 : 0 : properties.insert( QStringLiteral( "color" ), QStringLiteral( "255,0,0,75" ) );
48 : 0 : properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
49 : 0 : properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
50 : 0 : mFrameSymbol.reset( QgsFillSymbol::createSimple( properties ) );
51 : :
52 : 0 : mExtentLayer->setRenderer( new QgsSingleSymbolRenderer( mFrameSymbol->clone() ) );
53 : 0 : }
54 : :
55 : 0 : void QgsLayoutItemMapOverview::draw( QPainter *painter )
56 : : {
57 : 0 : if ( !mEnabled || !mFrameMap || !mMap || !mMap->layout() )
58 : : {
59 : 0 : return;
60 : : }
61 : 0 : if ( !painter )
62 : : {
63 : 0 : return;
64 : : }
65 : :
66 : 0 : const QgsLayoutItemMap *overviewFrameMap = linkedMap();
67 : 0 : if ( !overviewFrameMap )
68 : : {
69 : 0 : return;
70 : : }
71 : :
72 : : //get polygon for other overview frame map's extent (use visibleExtentPolygon as it accounts for map rotation)
73 : 0 : QPolygonF otherExtent = overviewFrameMap->visibleExtentPolygon();
74 : 0 : if ( overviewFrameMap->crs() !=
75 : 0 : mMap->crs() )
76 : : {
77 : 0 : QgsGeometry g = QgsGeometry::fromQPolygonF( otherExtent );
78 : :
79 : : // reproject extent
80 : 0 : QgsCoordinateTransform ct( overviewFrameMap->crs(),
81 : 0 : mMap->crs(), mLayout->project() );
82 : 0 : g = g.densifyByCount( 20 );
83 : : try
84 : : {
85 : 0 : g.transform( ct );
86 : 0 : }
87 : : catch ( QgsCsException & )
88 : : {
89 : 0 : }
90 : :
91 : 0 : otherExtent = g.asQPolygonF();
92 : 0 : }
93 : :
94 : : //get current map's extent as a QPolygonF
95 : 0 : QPolygonF thisExtent = mMap->visibleExtentPolygon();
96 : : //intersect the two
97 : 0 : QPolygonF intersectExtent = thisExtent.intersected( otherExtent );
98 : :
99 : : //setup painter scaling to dots so that raster symbology is drawn to scale
100 : 0 : double dotsPerMM = painter->device()->logicalDpiX() / 25.4;
101 : :
102 : : //setup render context
103 : 0 : QgsRenderContext context = QgsLayoutUtils::createRenderContextForLayout( mLayout, painter );
104 : 0 : context.setForceVectorOutput( true );
105 : 0 : QgsExpressionContext expressionContext = createExpressionContext();
106 : 0 : context.setExpressionContext( expressionContext );
107 : :
108 : 0 : QgsScopedQPainterState painterState( painter );
109 : 0 : context.setPainterFlagsUsingContext( painter );
110 : :
111 : 0 : painter->setCompositionMode( mBlendMode );
112 : 0 : painter->translate( mMap->mXOffset, mMap->mYOffset );
113 : 0 : painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
114 : :
115 : 0 : mFrameSymbol->startRender( context );
116 : :
117 : : //construct a polygon corresponding to the intersecting map extent
118 : : //need to scale line to dots, rather then mm, since the painter has been scaled to dots
119 : 0 : QTransform mapTransform;
120 : 0 : QPolygonF thisRectPoly = QPolygonF( QRectF( 0, 0, dotsPerMM * mMap->rect().width(), dotsPerMM * mMap->rect().height() ) );
121 : :
122 : : //workaround QT Bug #21329
123 : 0 : thisRectPoly.pop_back();
124 : 0 : thisExtent.pop_back();
125 : :
126 : : //create transform from map coordinates to painter coordinates
127 : 0 : QTransform::quadToQuad( thisExtent, thisRectPoly, mapTransform );
128 : 0 : QPolygonF intersectPolygon;
129 : 0 : intersectPolygon = mapTransform.map( intersectExtent );
130 : :
131 : 0 : QVector<QPolygonF> rings; //empty list
132 : 0 : if ( !mInverted )
133 : : {
134 : : //Render the intersecting map extent
135 : 0 : mFrameSymbol->renderPolygon( intersectPolygon, &rings, nullptr, context );
136 : 0 : }
137 : : else
138 : : {
139 : : //We are inverting the overview frame (ie, shading outside the intersecting extent)
140 : : //Construct a polygon corresponding to the overview map extent
141 : 0 : QPolygonF outerPolygon;
142 : 0 : outerPolygon << QPointF( 0, 0 )
143 : 0 : << QPointF( mMap->rect().width() * dotsPerMM, 0 )
144 : 0 : << QPointF( mMap->rect().width() * dotsPerMM, mMap->rect().height() * dotsPerMM )
145 : 0 : << QPointF( 0, mMap->rect().height() * dotsPerMM )
146 : 0 : << QPointF( 0, 0 );
147 : :
148 : : //Intersecting extent is an inner ring for the shaded area
149 : 0 : rings.append( intersectPolygon );
150 : 0 : mFrameSymbol->renderPolygon( outerPolygon, &rings, nullptr, context );
151 : 0 : }
152 : :
153 : 0 : mFrameSymbol->stopRender( context );
154 : 0 : }
155 : :
156 : 0 : bool QgsLayoutItemMapOverview::writeXml( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
157 : : {
158 : 0 : if ( elem.isNull() )
159 : : {
160 : 0 : return false;
161 : : }
162 : :
163 : : //overview map frame
164 : 0 : QDomElement overviewFrameElem = doc.createElement( QStringLiteral( "ComposerMapOverview" ) );
165 : :
166 : 0 : overviewFrameElem.setAttribute( QStringLiteral( "frameMap" ), mFrameMap ? mFrameMap ->uuid() : QString() );
167 : 0 : overviewFrameElem.setAttribute( QStringLiteral( "blendMode" ), QgsPainting::getBlendModeEnum( mBlendMode ) );
168 : 0 : overviewFrameElem.setAttribute( QStringLiteral( "inverted" ), mInverted );
169 : 0 : overviewFrameElem.setAttribute( QStringLiteral( "centered" ), mCentered );
170 : :
171 : 0 : QDomElement frameStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mFrameSymbol.get(), doc, context );
172 : 0 : overviewFrameElem.appendChild( frameStyleElem );
173 : :
174 : 0 : bool ok = QgsLayoutItemMapItem::writeXml( overviewFrameElem, doc, context );
175 : 0 : elem.appendChild( overviewFrameElem );
176 : 0 : return ok;
177 : 0 : }
178 : :
179 : 0 : bool QgsLayoutItemMapOverview::readXml( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
180 : : {
181 : 0 : Q_UNUSED( doc )
182 : 0 : if ( itemElem.isNull() )
183 : : {
184 : 0 : return false;
185 : : }
186 : :
187 : 0 : bool ok = QgsLayoutItemMapItem::readXml( itemElem, doc, context );
188 : :
189 : 0 : mFrameMapUuid = itemElem.attribute( QStringLiteral( "frameMap" ) );
190 : 0 : setLinkedMap( nullptr );
191 : :
192 : 0 : mBlendMode = QgsPainting::getCompositionMode( static_cast< QgsPainting::BlendMode >( itemElem.attribute( QStringLiteral( "blendMode" ), QStringLiteral( "0" ) ).toUInt() ) );
193 : 0 : mInverted = ( itemElem.attribute( QStringLiteral( "inverted" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
194 : 0 : mCentered = ( itemElem.attribute( QStringLiteral( "centered" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
195 : :
196 : 0 : QDomElement frameStyleElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
197 : 0 : if ( !frameStyleElem.isNull() )
198 : : {
199 : 0 : mFrameSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( frameStyleElem, context ) );
200 : 0 : }
201 : 0 : return ok;
202 : 0 : }
203 : :
204 : 0 : void QgsLayoutItemMapOverview::finalizeRestoreFromXml()
205 : : {
206 : 0 : if ( !mFrameMapUuid.isEmpty() )
207 : : {
208 : 0 : setLinkedMap( qobject_cast< QgsLayoutItemMap * >( mLayout->itemByUuid( mFrameMapUuid, true ) ) );
209 : 0 : }
210 : 0 : }
211 : :
212 : 0 : bool QgsLayoutItemMapOverview::usesAdvancedEffects() const
213 : : {
214 : 0 : return mBlendMode != QPainter::CompositionMode_SourceOver;
215 : : }
216 : :
217 : 0 : void QgsLayoutItemMapOverview::setLinkedMap( QgsLayoutItemMap *map )
218 : : {
219 : 0 : if ( mFrameMap == map )
220 : : {
221 : : //no change
222 : 0 : return;
223 : : }
224 : :
225 : : //disconnect old map
226 : 0 : if ( mFrameMap )
227 : : {
228 : 0 : disconnect( mFrameMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemMapOverview::overviewExtentChanged );
229 : 0 : disconnect( mFrameMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemMapOverview::overviewExtentChanged );
230 : 0 : }
231 : 0 : mFrameMap = map;
232 : : //connect to new map signals
233 : 0 : connectSignals();
234 : 0 : mMap->invalidateCache();
235 : 0 : }
236 : :
237 : 0 : QgsLayoutItemMap *QgsLayoutItemMapOverview::linkedMap()
238 : : {
239 : 0 : return mFrameMap;
240 : : }
241 : :
242 : 0 : void QgsLayoutItemMapOverview::connectSignals()
243 : : {
244 : 0 : if ( !mMap )
245 : : {
246 : 0 : return;
247 : : }
248 : :
249 : 0 : if ( mFrameMap )
250 : : {
251 : 0 : connect( mFrameMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemMapOverview::overviewExtentChanged );
252 : 0 : connect( mFrameMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemMapOverview::overviewExtentChanged );
253 : 0 : }
254 : 0 : }
255 : 0 :
256 : 0 : QgsVectorLayer *QgsLayoutItemMapOverview::asMapLayer()
257 : : {
258 : 0 : if ( !mEnabled || !mFrameMap || !mMap || !mMap->layout() )
259 : : {
260 : 0 : return nullptr;
261 : 0 : }
262 : :
263 : 0 : const QgsLayoutItemMap *overviewFrameMap = linkedMap();
264 : 0 : if ( !overviewFrameMap )
265 : : {
266 : 0 : return nullptr;
267 : : }
268 : :
269 : : //get polygon for other overview frame map's extent (use visibleExtentPolygon as it accounts for map rotation)
270 : 0 : QPolygonF otherExtent = overviewFrameMap->visibleExtentPolygon();
271 : 0 : QgsGeometry g = QgsGeometry::fromQPolygonF( otherExtent );
272 : :
273 : 0 : if ( overviewFrameMap->crs() != mMap->crs() )
274 : : {
275 : : // reproject extent
276 : 0 : QgsCoordinateTransform ct( overviewFrameMap->crs(),
277 : 0 : mMap->crs(), mLayout->project() );
278 : 0 : g = g.densifyByCount( 20 );
279 : : try
280 : : {
281 : 0 : g.transform( ct );
282 : 0 : }
283 : : catch ( QgsCsException & )
284 : : {
285 : 0 : }
286 : 0 : }
287 : :
288 : : //get current map's extent as a QPolygonF
289 : 0 : QPolygonF thisExtent = mMap->visibleExtentPolygon();
290 : 0 : QgsGeometry thisGeom = QgsGeometry::fromQPolygonF( thisExtent );
291 : : //intersect the two
292 : 0 : QgsGeometry intersectExtent = thisGeom.intersection( g );
293 : :
294 : 0 : mExtentLayer->setBlendMode( mBlendMode );
295 : :
296 : 0 : static_cast< QgsSingleSymbolRenderer * >( mExtentLayer->renderer() )->setSymbol( mFrameSymbol->clone() );
297 : 0 : mExtentLayer->dataProvider()->truncate();
298 : 0 : mExtentLayer->setCrs( mMap->crs() );
299 : :
300 : 0 : if ( mInverted )
301 : : {
302 : 0 : intersectExtent = thisGeom.difference( intersectExtent );
303 : 0 : }
304 : :
305 : 0 : QgsFeature f;
306 : 0 : f.setGeometry( intersectExtent );
307 : 0 : mExtentLayer->dataProvider()->addFeature( f );
308 : :
309 : 0 : return mExtentLayer.get();
310 : 0 : }
311 : :
312 : 0 : QgsMapLayer *QgsLayoutItemMapOverview::mapLayer()
313 : : {
314 : 0 : return mExtentLayer.get();
315 : : }
316 : :
317 : 0 : bool QgsLayoutItemMapOverview::accept( QgsStyleEntityVisitorInterface *visitor ) const
318 : : {
319 : 0 : if ( mFrameSymbol )
320 : : {
321 : 0 : QgsStyleSymbolEntity entity( mFrameSymbol.get() );
322 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "overview" ), QObject::tr( "Overview" ) ) ) )
323 : 0 : return false;
324 : 0 : }
325 : :
326 : 0 : return true;
327 : 0 : }
328 : :
329 : 0 : void QgsLayoutItemMapOverview::setFrameSymbol( QgsFillSymbol *symbol )
330 : : {
331 : 0 : mFrameSymbol.reset( symbol );
332 : 0 : }
333 : :
334 : 0 : QgsFillSymbol *QgsLayoutItemMapOverview::frameSymbol()
335 : : {
336 : 0 : return mFrameSymbol.get();
337 : : }
338 : :
339 : 0 : const QgsFillSymbol *QgsLayoutItemMapOverview::frameSymbol() const
340 : : {
341 : 0 : return mFrameSymbol.get();
342 : : }
343 : :
344 : 0 : void QgsLayoutItemMapOverview::setBlendMode( const QPainter::CompositionMode blendMode )
345 : : {
346 : 0 : mBlendMode = blendMode;
347 : 0 : }
348 : :
349 : 0 : void QgsLayoutItemMapOverview::setInverted( const bool inverted )
350 : : {
351 : 0 : mInverted = inverted;
352 : 0 : }
353 : :
354 : 0 : void QgsLayoutItemMapOverview::setCentered( const bool centered )
355 : : {
356 : 0 : mCentered = centered;
357 : 0 : overviewExtentChanged();
358 : 0 : }
359 : :
360 : 0 : void QgsLayoutItemMapOverview::overviewExtentChanged()
361 : : {
362 : 0 : if ( !mMap )
363 : : {
364 : 0 : return;
365 : : }
366 : :
367 : : //if using overview centering, update the map's extent
368 : 0 : if ( mMap->layout() && mCentered && mFrameMap )
369 : : {
370 : 0 : QgsRectangle extent = mMap->extent();
371 : 0 : QgsRectangle otherExtent = mFrameMap->extent();
372 : :
373 : 0 : QgsPointXY center = otherExtent.center();
374 : 0 : QgsRectangle movedExtent( center.x() - extent.width() / 2,
375 : 0 : center.y() - extent.height() / 2,
376 : 0 : center.x() - extent.width() / 2 + extent.width(),
377 : 0 : center.y() - extent.height() / 2 + extent.height() );
378 : 0 : mMap->setExtent( movedExtent );
379 : 0 : }
380 : :
381 : : //repaint map so that overview gets updated
382 : 0 : mMap->invalidateCache();
383 : 0 : }
384 : :
385 : :
386 : : //
387 : : // QgsLayoutItemMapOverviewStack
388 : : //
389 : :
390 : 0 : QgsLayoutItemMapOverviewStack::QgsLayoutItemMapOverviewStack( QgsLayoutItemMap *map )
391 : 0 : : QgsLayoutItemMapItemStack( map )
392 : 0 : {
393 : :
394 : 0 : }
395 : :
396 : 0 : void QgsLayoutItemMapOverviewStack::addOverview( QgsLayoutItemMapOverview *overview )
397 : : {
398 : 0 : QgsLayoutItemMapItemStack::addItem( overview );
399 : 0 : }
400 : :
401 : 0 : void QgsLayoutItemMapOverviewStack::removeOverview( const QString &overviewId )
402 : : {
403 : 0 : QgsLayoutItemMapItemStack::removeItem( overviewId );
404 : 0 : }
405 : :
406 : 0 : void QgsLayoutItemMapOverviewStack::moveOverviewUp( const QString &overviewId )
407 : : {
408 : 0 : QgsLayoutItemMapItemStack::moveItemUp( overviewId );
409 : 0 : }
410 : :
411 : 0 : void QgsLayoutItemMapOverviewStack::moveOverviewDown( const QString &overviewId )
412 : : {
413 : 0 : QgsLayoutItemMapItemStack::moveItemDown( overviewId );
414 : 0 : }
415 : :
416 : 0 : QgsLayoutItemMapOverview *QgsLayoutItemMapOverviewStack::overview( const QString &overviewId ) const
417 : : {
418 : 0 : QgsLayoutItemMapItem *item = QgsLayoutItemMapItemStack::item( overviewId );
419 : 0 : return qobject_cast<QgsLayoutItemMapOverview *>( item );
420 : : }
421 : :
422 : 0 : QgsLayoutItemMapOverview *QgsLayoutItemMapOverviewStack::overview( const int index ) const
423 : : {
424 : 0 : QgsLayoutItemMapItem *item = QgsLayoutItemMapItemStack::item( index );
425 : 0 : return qobject_cast<QgsLayoutItemMapOverview *>( item );
426 : : }
427 : :
428 : 0 : QgsLayoutItemMapOverview &QgsLayoutItemMapOverviewStack::operator[]( int idx )
429 : : {
430 : 0 : QgsLayoutItemMapItem *item = mItems.at( idx );
431 : 0 : QgsLayoutItemMapOverview *overview = qobject_cast<QgsLayoutItemMapOverview *>( item );
432 : 0 : return *overview;
433 : : }
434 : :
435 : 0 : QList<QgsLayoutItemMapOverview *> QgsLayoutItemMapOverviewStack::asList() const
436 : : {
437 : 0 : QList< QgsLayoutItemMapOverview * > list;
438 : 0 : QList< QgsLayoutItemMapItem * >::const_iterator it = mItems.begin();
439 : 0 : for ( ; it != mItems.end(); ++it )
440 : : {
441 : 0 : QgsLayoutItemMapOverview *overview = qobject_cast<QgsLayoutItemMapOverview *>( *it );
442 : 0 : if ( overview )
443 : : {
444 : 0 : list.append( overview );
445 : 0 : }
446 : 0 : }
447 : 0 : return list;
448 : 0 : }
449 : :
450 : 0 : bool QgsLayoutItemMapOverviewStack::readXml( const QDomElement &elem, const QDomDocument &doc, const QgsReadWriteContext &context )
451 : : {
452 : 0 : removeItems();
453 : :
454 : : //read overview stack
455 : 0 : QDomNodeList mapOverviewNodeList = elem.elementsByTagName( QStringLiteral( "ComposerMapOverview" ) );
456 : 0 : for ( int i = 0; i < mapOverviewNodeList.size(); ++i )
457 : : {
458 : 0 : QDomElement mapOverviewElem = mapOverviewNodeList.at( i ).toElement();
459 : 0 : QgsLayoutItemMapOverview *mapOverview = new QgsLayoutItemMapOverview( mapOverviewElem.attribute( QStringLiteral( "name" ) ), mMap );
460 : 0 : mapOverview->readXml( mapOverviewElem, doc, context );
461 : 0 : mItems.append( mapOverview );
462 : 0 : }
463 : :
464 : : return true;
465 : 0 : }
466 : :
467 : 0 : QList<QgsMapLayer *> QgsLayoutItemMapOverviewStack::modifyMapLayerList( const QList<QgsMapLayer *> &layers )
468 : : {
469 : 0 : QList<QgsMapLayer *> res = layers;
470 : 0 : res.reserve( layers.count() + mItems.count() );
471 : 0 : for ( QgsLayoutItemMapItem *item : std::as_const( mItems ) )
472 : : {
473 : 0 : if ( !item )
474 : 0 : continue;
475 : :
476 : 0 : QgsVectorLayer *l = static_cast< QgsLayoutItemMapOverview * >( item )->asMapLayer();
477 : 0 : if ( !l )
478 : 0 : continue;
479 : :
480 : 0 : switch ( item->stackingPosition() )
481 : : {
482 : : case QgsLayoutItemMapItem::StackAboveMapLabels:
483 : 0 : continue;
484 : :
485 : : case QgsLayoutItemMapItem::StackAboveMapLayer:
486 : : case QgsLayoutItemMapItem::StackBelowMapLayer:
487 : : {
488 : 0 : QgsMapLayer *stackLayer = item->stackingLayer();
489 : 0 : if ( !stackLayer )
490 : 0 : continue;
491 : :
492 : 0 : auto pos = std::find( res.begin(), res.end(), stackLayer );
493 : 0 : if ( pos == res.end() )
494 : 0 : continue;
495 : :
496 : 0 : if ( item->stackingPosition() == QgsLayoutItemMapItem::StackBelowMapLayer )
497 : : {
498 : 0 : pos++;
499 : 0 : if ( pos == res.end() )
500 : : {
501 : 0 : res.push_back( l );
502 : 0 : break;
503 : : }
504 : 0 : }
505 : 0 : res.insert( pos, l );
506 : 0 : break;
507 : : }
508 : :
509 : : case QgsLayoutItemMapItem::StackBelowMap:
510 : 0 : res.push_back( l );
511 : 0 : break;
512 : :
513 : : case QgsLayoutItemMapItem::StackBelowMapLabels:
514 : 0 : res.push_front( l );
515 : 0 : break;
516 : : }
517 : : }
518 : :
519 : 0 : return res;
520 : 0 : }
|