1 /*************************************************************************** 2 testqgsmaptoollabel.cpp 3 ----------------------- 4 Date : July 2019 5 Copyright : (C) 2019 by Nyall Dawson 6 Email : nyall dot dawson at gmail dot com 7 *************************************************************************** 8 * * 9 * This program is free software; you can redistribute it and/or modify * 10 * it under the terms of the GNU General Public License as published by * 11 * the Free Software Foundation; either version 2 of the License, or * 12 * (at your option) any later version. * 13 * * 14 ***************************************************************************/ 15 16 #include "qgstest.h" 17 #include <QObject> 18 #include <QString> 19 #include <QMouseEvent> 20 21 #include "qgsapplication.h" 22 #include "qgsvectorlayer.h" 23 #include "qgsvectordataprovider.h" 24 #include "qgsgeometry.h" 25 #include "qgsmapcanvas.h" 26 #include "qgsmaptoollabel.h" 27 #include "qgsfontutils.h" 28 #include "qgsvectorlayerlabelprovider.h" 29 #include "qgsvectorlayerlabeling.h" 30 31 class TestQgsMapToolLabel : public QObject 32 { 33 Q_OBJECT 34 35 public: 36 TestQgsMapToolLabel() = default; 37 38 private: 39 40 private slots: 41 initTestCase()42 void initTestCase() 43 { 44 QgsApplication::init(); 45 QgsApplication::initQgis(); 46 47 } 48 cleanupTestCase()49 void cleanupTestCase() 50 { 51 QgsApplication::exitQgis(); 52 } 53 testSelectLabel()54 void testSelectLabel() 55 { 56 QgsVectorLayer *vl1 = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:3946&field=text:string" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) ); 57 QVERIFY( vl1->isValid() ); 58 QgsFeature f1; 59 f1.setAttributes( QgsAttributes() << QStringLiteral( "label" ) ); 60 f1.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( 1, 1 ) ) ); 61 QVERIFY( vl1->dataProvider()->addFeature( f1 ) ); 62 f1.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( 3, 3 ) ) ); 63 f1.setAttributes( QgsAttributes() << QStringLiteral( "l" ) ); 64 QVERIFY( vl1->dataProvider()->addFeature( f1 ) ); 65 QgsProject::instance()->addMapLayer( vl1 ); 66 67 QgsVectorLayer *vl2 = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:3946&field=text:string" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) ); 68 QVERIFY( vl2->isValid() ); 69 f1.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( 1, 1 ) ) ); 70 f1.setAttributes( QgsAttributes() << QStringLiteral( "label" ) ); 71 QVERIFY( vl2->dataProvider()->addFeature( f1 ) ); 72 f1.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( 3, 3 ) ) ); 73 f1.setAttributes( QgsAttributes() << QStringLiteral( "label2" ) ); 74 QVERIFY( vl2->dataProvider()->addFeature( f1 ) ); 75 f1.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( 3, 1 ) ) ); 76 f1.setAttributes( QgsAttributes() << QStringLiteral( "label3" ) ); 77 QVERIFY( vl2->dataProvider()->addFeature( f1 ) ); 78 QgsProject::instance()->addMapLayer( vl2 ); 79 80 std::unique_ptr< QgsMapCanvas > canvas = qgis::make_unique< QgsMapCanvas >(); 81 canvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) ); 82 canvas->setLayers( QList<QgsMapLayer *>() << vl1 << vl2 ); 83 84 QgsMapSettings mapSettings; 85 mapSettings.setOutputSize( QSize( 500, 500 ) ); 86 mapSettings.setExtent( QgsRectangle( -1, -1, 4, 4 ) ); 87 QVERIFY( mapSettings.hasValidSettings() ); 88 89 mapSettings.setLayers( QList<QgsMapLayer *>() << vl1 << vl2 ); 90 91 canvas->setFrameStyle( QFrame::NoFrame ); 92 canvas->resize( 500, 500 ); 93 canvas->setExtent( QgsRectangle( -1, -1, 4, 4 ) ); 94 canvas->show(); // to make the canvas resize 95 canvas->hide(); 96 QCOMPARE( canvas->mapSettings().outputSize(), QSize( 500, 500 ) ); 97 QCOMPARE( canvas->mapSettings().visibleExtent(), QgsRectangle( -1, -1, 4, 4 ) ); 98 99 std::unique_ptr< QgsMapToolLabel > tool( new QgsMapToolLabel( canvas.get() ) ); 100 101 // no labels yet 102 QgsPointXY pt; 103 pt = tool->canvas()->mapSettings().mapToPixel().transform( 1, 1 ); 104 std::unique_ptr< QMouseEvent > event( new QMouseEvent( 105 QEvent::MouseButtonPress, 106 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 107 ) ); 108 QgsLabelPosition pos; 109 QVERIFY( !tool->labelAtPosition( event.get(), pos ) ); 110 111 // add some labels 112 QgsPalLayerSettings pls1; 113 pls1.fieldName = QStringLiteral( "text" ); 114 pls1.placement = QgsPalLayerSettings::OverPoint; 115 pls1.quadOffset = QgsPalLayerSettings::QuadrantOver; 116 pls1.displayAll = true; 117 QgsTextFormat format = pls1.format(); 118 format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) ); 119 format.setSize( 12 ); 120 pls1.setFormat( format ); 121 122 vl1->setLabeling( new QgsVectorLayerSimpleLabeling( pls1 ) ); 123 vl1->setLabelsEnabled( true ); 124 125 QEventLoop loop; 126 connect( canvas.get(), &QgsMapCanvas::mapCanvasRefreshed, &loop, &QEventLoop::quit ); 127 canvas->refreshAllLayers(); 128 canvas->show(); 129 loop.exec(); 130 131 QVERIFY( canvas->labelingResults() ); 132 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 133 QCOMPARE( pos.layerID, vl1->id() ); 134 QCOMPARE( pos.labelText, QStringLiteral( "label" ) ); 135 136 pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 3 ); 137 event = qgis::make_unique< QMouseEvent >( 138 QEvent::MouseButtonPress, 139 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 140 ); 141 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 142 QCOMPARE( pos.layerID, vl1->id() ); 143 QCOMPARE( pos.labelText, QStringLiteral( "l" ) ); 144 145 pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 1 ); 146 event = qgis::make_unique< QMouseEvent >( 147 QEvent::MouseButtonPress, 148 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 149 ); 150 QVERIFY( !tool->labelAtPosition( event.get(), pos ) ); 151 152 // label second layer 153 vl2->setLabeling( new QgsVectorLayerSimpleLabeling( pls1 ) ); 154 vl2->setLabelsEnabled( true ); 155 156 canvas->refreshAllLayers(); 157 canvas->show(); 158 loop.exec(); 159 160 // should prioritize current layer 161 canvas->setCurrentLayer( vl1 ); 162 pt = tool->canvas()->mapSettings().mapToPixel().transform( 1, 1 ); 163 event = qgis::make_unique< QMouseEvent >( 164 QEvent::MouseButtonPress, 165 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 166 ); 167 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 168 QCOMPARE( pos.layerID, vl1->id() ); 169 QCOMPARE( pos.labelText, QStringLiteral( "label" ) ); 170 171 pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 3 ); 172 event = qgis::make_unique< QMouseEvent >( 173 QEvent::MouseButtonPress, 174 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 175 ); 176 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 177 QCOMPARE( pos.layerID, vl1->id() ); 178 QCOMPARE( pos.labelText, QStringLiteral( "l" ) ); 179 180 //... but fallback to any labels if nothing in current layer 181 pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 1 ); 182 event = qgis::make_unique< QMouseEvent >( 183 QEvent::MouseButtonPress, 184 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 185 ); 186 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 187 QCOMPARE( pos.layerID, vl2->id() ); 188 QCOMPARE( pos.labelText, QStringLiteral( "label3" ) ); 189 190 canvas->setCurrentLayer( vl2 ); 191 pt = tool->canvas()->mapSettings().mapToPixel().transform( 1, 1 ); 192 event = qgis::make_unique< QMouseEvent >( 193 QEvent::MouseButtonPress, 194 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 195 ); 196 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 197 QCOMPARE( pos.layerID, vl2->id() ); 198 QCOMPARE( pos.labelText, QStringLiteral( "label" ) ); 199 200 pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 3 ); 201 event = qgis::make_unique< QMouseEvent >( 202 QEvent::MouseButtonPress, 203 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 204 ); 205 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 206 QCOMPARE( pos.layerID, vl2->id() ); 207 QCOMPARE( pos.labelText, QStringLiteral( "label2" ) ); 208 pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 1 ); 209 event = qgis::make_unique< QMouseEvent >( 210 QEvent::MouseButtonPress, 211 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 212 ); 213 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 214 QCOMPARE( pos.layerID, vl2->id() ); 215 QCOMPARE( pos.labelText, QStringLiteral( "label3" ) ); 216 217 canvas->setCurrentLayer( nullptr ); 218 219 // when multiple candidates exist, pick the smallest 220 pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 3 ); 221 event = qgis::make_unique< QMouseEvent >( 222 QEvent::MouseButtonPress, 223 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 224 ); 225 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 226 QCOMPARE( pos.layerID, vl1->id() ); 227 QCOMPARE( pos.labelText, QStringLiteral( "l" ) ); 228 229 QgsProject::instance()->clear(); 230 } 231 testAlignment()232 void testAlignment() 233 { 234 QgsVectorLayer *vl1 = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:3946&field=halig:string&field=valig:string" ), QStringLiteral( "vl1" ), QStringLiteral( "memory" ) ); 235 QVERIFY( vl1->isValid() ); 236 QgsProject::instance()->addMapLayer( vl1 ); 237 QgsFeature f1; 238 f1.setAttributes( QgsAttributes() << QStringLiteral( "right" ) << QStringLiteral( "top" ) ); 239 f1.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( 1, 1 ) ) ); 240 QVERIFY( vl1->dataProvider()->addFeature( f1 ) ); 241 f1.setGeometry( QgsGeometry::fromPointXY( QgsPointXY( 3, 3 ) ) ); 242 f1.setAttributes( QgsAttributes() << QStringLiteral( "center" ) << QStringLiteral( "base" ) ); 243 QVERIFY( vl1->dataProvider()->addFeature( f1 ) ); 244 245 std::unique_ptr< QgsMapCanvas > canvas = qgis::make_unique< QgsMapCanvas >(); 246 canvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3946" ) ) ); 247 canvas->setLayers( QList<QgsMapLayer *>() << vl1 ); 248 249 QgsMapSettings mapSettings; 250 mapSettings.setOutputSize( QSize( 500, 500 ) ); 251 mapSettings.setExtent( QgsRectangle( -1, -1, 4, 4 ) ); 252 QVERIFY( mapSettings.hasValidSettings() ); 253 254 mapSettings.setLayers( QList<QgsMapLayer *>() << vl1 ); 255 256 canvas->setFrameStyle( QFrame::NoFrame ); 257 canvas->resize( 500, 500 ); 258 canvas->setExtent( QgsRectangle( -1, -1, 4, 4 ) ); 259 canvas->show(); // to make the canvas resize 260 canvas->hide(); 261 QCOMPARE( canvas->mapSettings().outputSize(), QSize( 500, 500 ) ); 262 QCOMPARE( canvas->mapSettings().visibleExtent(), QgsRectangle( -1, -1, 4, 4 ) ); 263 264 std::unique_ptr< QgsMapToolLabel > tool( new QgsMapToolLabel( canvas.get() ) ); 265 266 // add some labels 267 QgsPalLayerSettings pls1; 268 pls1.fieldName = QStringLiteral( "'label'" ); 269 pls1.isExpression = true; 270 pls1.placement = QgsPalLayerSettings::OverPoint; 271 pls1.quadOffset = QgsPalLayerSettings::QuadrantOver; 272 pls1.displayAll = true; 273 QgsTextFormat format = pls1.format(); 274 format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ) ); 275 format.setSize( 12 ); 276 pls1.setFormat( format ); 277 278 vl1->setLabeling( new QgsVectorLayerSimpleLabeling( pls1 ) ); 279 vl1->setLabelsEnabled( true ); 280 281 QEventLoop loop; 282 connect( canvas.get(), &QgsMapCanvas::mapCanvasRefreshed, &loop, &QEventLoop::quit ); 283 canvas->refreshAllLayers(); 284 canvas->show(); 285 loop.exec(); 286 287 QVERIFY( canvas->labelingResults() ); 288 QgsPointXY pt; 289 pt = tool->canvas()->mapSettings().mapToPixel().transform( 1, 1 ); 290 std::unique_ptr< QMouseEvent > event( new QMouseEvent( 291 QEvent::MouseButtonPress, 292 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 293 ) ); 294 QgsLabelPosition pos; 295 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 296 QCOMPARE( pos.layerID, vl1->id() ); 297 QCOMPARE( pos.labelText, QStringLiteral( "label" ) ); 298 tool->mCurrentLabel = QgsMapToolLabel::LabelDetails( pos ); 299 300 // defaults to bottom left 301 QString hali, vali; 302 tool->currentAlignment( hali, vali ); 303 QCOMPARE( hali, QStringLiteral( "Left" ) ); 304 QCOMPARE( vali, QStringLiteral( "Bottom" ) ); 305 306 // using field bound alignment 307 pls1.dataDefinedProperties().setProperty( QgsPalLayerSettings::Hali, QgsProperty::fromField( QStringLiteral( "halig" ) ) ); 308 pls1.dataDefinedProperties().setProperty( QgsPalLayerSettings::Vali, QgsProperty::fromField( QStringLiteral( "valig" ) ) ); 309 vl1->setLabeling( new QgsVectorLayerSimpleLabeling( pls1 ) ); 310 311 canvas->refreshAllLayers(); 312 canvas->show(); 313 loop.exec(); 314 315 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 316 QCOMPARE( pos.layerID, vl1->id() ); 317 QCOMPARE( pos.labelText, QStringLiteral( "label" ) ); 318 tool->mCurrentLabel = QgsMapToolLabel::LabelDetails( pos ); 319 320 tool->currentAlignment( hali, vali ); 321 QCOMPARE( hali, QStringLiteral( "right" ) ); 322 QCOMPARE( vali, QStringLiteral( "top" ) ); 323 324 pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 3 ); 325 event = qgis::make_unique< QMouseEvent >( 326 QEvent::MouseButtonPress, 327 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 328 ); 329 330 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 331 QCOMPARE( pos.layerID, vl1->id() ); 332 QCOMPARE( pos.labelText, QStringLiteral( "label" ) ); 333 tool->mCurrentLabel = QgsMapToolLabel::LabelDetails( pos ); 334 335 tool->currentAlignment( hali, vali ); 336 QCOMPARE( hali, QStringLiteral( "center" ) ); 337 QCOMPARE( vali, QStringLiteral( "base" ) ); 338 339 // now try with expression based alignment 340 pls1.dataDefinedProperties().setProperty( QgsPalLayerSettings::Hali, QgsProperty::fromExpression( QStringLiteral( "case when $id % 2 = 0 then 'right' else 'left' end" ) ) ); 341 pls1.dataDefinedProperties().setProperty( QgsPalLayerSettings::Vali, QgsProperty::fromExpression( QStringLiteral( "case when $id % 2 = 0 then 'half' else 'cap' end" ) ) ); 342 vl1->setLabeling( new QgsVectorLayerSimpleLabeling( pls1 ) ); 343 344 canvas->refreshAllLayers(); 345 canvas->show(); 346 loop.exec(); 347 348 pt = tool->canvas()->mapSettings().mapToPixel().transform( 1, 1 ); 349 event = qgis::make_unique< QMouseEvent >( 350 QEvent::MouseButtonPress, 351 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 352 ); 353 354 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 355 QCOMPARE( pos.layerID, vl1->id() ); 356 QCOMPARE( pos.labelText, QStringLiteral( "label" ) ); 357 tool->mCurrentLabel = QgsMapToolLabel::LabelDetails( pos ); 358 359 tool->currentAlignment( hali, vali ); 360 QCOMPARE( hali, QStringLiteral( "left" ) ); 361 QCOMPARE( vali, QStringLiteral( "cap" ) ); 362 363 pt = tool->canvas()->mapSettings().mapToPixel().transform( 3, 3 ); 364 event = qgis::make_unique< QMouseEvent >( 365 QEvent::MouseButtonPress, 366 QPoint( std::round( pt.x() ), std::round( pt.y() ) ), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier 367 ); 368 369 QVERIFY( tool->labelAtPosition( event.get(), pos ) ); 370 QCOMPARE( pos.layerID, vl1->id() ); 371 QCOMPARE( pos.labelText, QStringLiteral( "label" ) ); 372 tool->mCurrentLabel = QgsMapToolLabel::LabelDetails( pos ); 373 374 tool->currentAlignment( hali, vali ); 375 QCOMPARE( hali, QStringLiteral( "right" ) ); 376 QCOMPARE( vali, QStringLiteral( "half" ) ); 377 } 378 }; 379 380 QGSTEST_MAIN( TestQgsMapToolLabel ) 381 #include "testqgsmaptoollabel.moc" 382