1 /***************************************************************************
2  qgisapp.cpp  -  description
3  -------------------
4 
5           begin                : Sat Jun 22 2002
6           copyright            : (C) 2002 by Gary E.Sherman
7           email                : sherman at mrcc.com
8           Romans 3:23=>Romans 6:23=>Romans 10:9,10=>Romans 12
9 ***************************************************************************/
10 
11 /***************************************************************************
12  *                                                                         *
13  *   This program is free software; you can redistribute it and/or modify  *
14  *   it under the terms of the GNU General Public License as published by  *
15  *   the Free Software Foundation; either version 2 of the License, or     *
16  *   (at your option) any later version.                                   *
17  *                                                                         *
18  ***************************************************************************/
19 
20 #include <QObject>
21 #include <QAction>
22 #include <QApplication>
23 #include <QBitmap>
24 #include <QCheckBox>
25 #include <QClipboard>
26 #include <QColor>
27 #include <QCursor>
28 #include <QDesktopServices>
29 #include <QDesktopWidget>
30 #include <QDialog>
31 #include <QDialogButtonBox>
32 #include <QDir>
33 #include <QEvent>
34 #include <QUrlQuery>
35 #include <QFile>
36 #include <QFileInfo>
37 #include <QImageWriter>
38 #include <QInputDialog>
39 #include <QKeyEvent>
40 #include <QLabel>
41 #include <QLibrary>
42 #include <QMenu>
43 #include <QMenuBar>
44 #include <QMessageBox>
45 #include <QPainter>
46 #include <QPictureIO>
47 #include <QPixmap>
48 #include <QPoint>
49 #include <QPrinter>
50 #include <QProcess>
51 #include <QProgressBar>
52 #include <QProgressDialog>
53 #include <QRegExp>
54 #include <QRegExpValidator>
55 #include <QScreen>
56 #include <QShortcut>
57 #include <QSpinBox>
58 #include <QSplashScreen>
59 #include <QUrl>
60 #include <QRegularExpression>
61 #ifndef QT_NO_SSL
62 #include <QSslConfiguration>
63 #endif
64 #include <QStatusBar>
65 #include <QStringList>
66 #include <QSysInfo>
67 #include <QTcpSocket>
68 #include <QTextStream>
69 #include <QtGlobal>
70 #include <QThread>
71 #include <QTimer>
72 #include <QToolButton>
73 #include <QUuid>
74 #include <QVBoxLayout>
75 #include <QWhatsThis>
76 #include <QWidgetAction>
77 #include <mutex>
78 
79 #include "qgssettingsregistrycore.h"
80 #include "qgsnetworkaccessmanager.h"
81 #include "qgsrelationmanager.h"
82 #include "qgsapplication.h"
83 #include "qgslayerstylingwidget.h"
84 #include "qgsdevtoolspanelwidget.h"
85 #include "qgstaskmanager.h"
86 #include "qgsweakrelation.h"
87 #include "qgsziputils.h"
88 #include "qgsbrowserguimodel.h"
89 #include "qgsvectorlayerjoinbuffer.h"
90 #include "qgsgeometryvalidationservice.h"
91 #include "qgssourceselectproviderregistry.h"
92 #include "qgssourceselectprovider.h"
93 #include "qgsprovidermetadata.h"
94 #include "qgsfixattributedialog.h"
95 #include "qgsprojecttimesettings.h"
96 #include "qgsmaplayertemporalproperties.h"
97 #include "qgsmaplayerutils.h"
98 #include "qgsmeshlayertemporalproperties.h"
99 #include "qgsvectorlayersavestyledialog.h"
100 #include "maptools/qgsappmaptools.h"
101 #include "qgsexpressioncontextutils.h"
102 #include "qgsprovidersublayerdetails.h"
103 #include "qgsproviderutils.h"
104 #include "qgsprovidersublayersdialog.h"
105 #include "qgsmaplayerfactory.h"
106 #include "qgsbrowserwidget.h"
107 #include "annotations/qgsannotationitempropertieswidget.h"
108 #include "qgsmaptoolmodifyannotation.h"
109 #include "qgsannotationlayer.h"
110 
111 #include "qgsanalysis.h"
112 #include "qgsgeometrycheckregistry.h"
113 
114 #include "options/qgscodeeditoroptions.h"
115 #include "options/qgsgpsdeviceoptions.h"
116 
117 #ifdef HAVE_3D
118 #include "qgs3d.h"
119 #include "qgs3danimationsettings.h"
120 #include "qgs3danimationwidget.h"
121 #include "qgs3dmapcanvasdockwidget.h"
122 #include "qgs3dmapcanvas.h"
123 #include "qgs3dmapsettings.h"
124 #include "qgscameracontroller.h"
125 #include "qgsflatterraingenerator.h"
126 #include "qgslayoutitem3dmap.h"
127 #include "processing/qgs3dalgorithms.h"
128 #include "qgs3dmaptoolmeasureline.h"
129 #include "qgs3dsymbolregistry.h"
130 #include "layout/qgslayout3dmapwidget.h"
131 #include "layout/qgslayoutviewrubberband.h"
132 #include "qgsvectorlayer3drendererwidget.h"
133 #include "qgsmeshlayer3drendererwidget.h"
134 #include "qgspointcloudlayer3drendererwidget.h"
135 #include "qgs3dapputils.h"
136 #include "qgs3doptions.h"
137 #endif
138 
139 #ifdef HAVE_GEOREFERENCER
140 #include "georeferencer/qgsgeorefmainwindow.h"
141 #endif
142 
143 #include "qgsgui.h"
144 #include "qgsnative.h"
145 #include "qgsdatasourceselectdialog.h"
146 
147 #ifdef HAVE_OPENCL
148 #include "qgsopenclutils.h"
149 #endif
150 
151 #include <QNetworkReply>
152 #include <QNetworkProxy>
153 #include <QAuthenticator>
154 
155 Q_GUI_EXPORT extern int qt_defaultDpiX();
156 
157 //
158 // Mac OS X Includes
159 // Must include before GEOS 3 due to unqualified use of 'Point'
160 //
161 #ifdef Q_OS_MACX
162 #include <ApplicationServices/ApplicationServices.h>
163 #include "qgsmacnative.h"
164 
165 // check macro breaks QItemDelegate
166 #ifdef check
167 #undef check
168 #endif
169 #endif
170 
171 //
172 // QGIS Specific Includes
173 //
174 
175 #include "qgscrashhandler.h"
176 
177 #include "qgisapp.h"
178 #include "qgisappinterface.h"
179 #include "qgisappstylesheet.h"
180 #include "qgis.h"
181 #include "qgisplugin.h"
182 #include "qgsabout.h"
183 #include "qgsabstractmaptoolhandler.h"
184 #include "qgsalignrasterdialog.h"
185 #include "qgsappauthrequesthandler.h"
186 #include "qgsappbrowserproviders.h"
187 #include "qgsapplayertreeviewmenuprovider.h"
188 #include "qgsapplication.h"
189 #include "qgsappsslerrorhandler.h"
190 #include "qgsactionmanager.h"
191 #include "qgsannotationmanager.h"
192 #include "qgsannotationregistry.h"
193 #include "qgsattributetabledialog.h"
194 #include "qgsattributedialog.h"
195 #include "qgsauthmanager.h"
196 #include "qgsauthguiutils.h"
197 #ifndef QT_NO_SSL
198 #include "qgsauthcertutils.h"
199 #include "qgsauthsslerrorsdialog.h"
200 #endif
201 #include "qgsappscreenshots.h"
202 #include "qgsapplicationexitblockerinterface.h"
203 #include "qgsbookmarks.h"
204 #include "qgsbookmarkeditordialog.h"
205 #include "qgsbrowserdockwidget.h"
206 #include "qgsadvanceddigitizingdockwidget.h"
207 #include "qgsclipboard.h"
208 #include "qgsconfigureshortcutsdialog.h"
209 #include "qgscoordinatetransform.h"
210 #include "qgscoordinateutils.h"
211 #include "qgscredentialdialog.h"
212 #include "qgscustomdrophandler.h"
213 #include "qgscustomprojectopenhandler.h"
214 #include "qgscustomization.h"
215 #include "qgscustomlayerorderwidget.h"
216 #include "qgscustomprojectiondialog.h"
217 #include "qgsdataitemproviderregistry.h"
218 #include "qgsdataitemguiproviderregistry.h"
219 #include "qgsdatasourceuri.h"
220 #include "qgsdatumtransformdialog.h"
221 #include "qgsdoublespinbox.h"
222 #include "qgsdockwidget.h"
223 #include "qgsdxfexport.h"
224 #include "qgsdxfexportdialog.h"
225 #include "qgsdwgimportdialog.h"
226 #include "qgsdecorationtitle.h"
227 #include "qgsdecorationcopyright.h"
228 #include "qgsdecorationimage.h"
229 #include "qgsdecorationnortharrow.h"
230 #include "qgsdecorationscalebar.h"
231 #include "qgsdecorationgrid.h"
232 #include "qgsdecorationlayoutextent.h"
233 #include "qgsencodingfiledialog.h"
234 #include "qgserror.h"
235 #include "qgserrordialog.h"
236 #include "qgseventtracing.h"
237 #include "qgsexception.h"
238 #include "qgsexpressionselectiondialog.h"
239 #include "qgsfeature.h"
240 #include "qgsfieldcalculator.h"
241 #include "qgsfieldformatter.h"
242 #include "qgsfieldformatterregistry.h"
243 #include "qgsfileutils.h"
244 #include "qgsformannotation.h"
245 #include "qgsgeos.h"
246 #include "qgsguiutils.h"
247 #include "qgshtmlannotation.h"
248 #include "qgsprojectionselectiondialog.h"
249 #include "qgsgpsinformationwidget.h"
250 #include "qgsguivectorlayertools.h"
251 #include "qgslabelingwidget.h"
252 #include "qgsdiagramproperties.h"
253 #include "qgslayerdefinition.h"
254 #include "qgslayertree.h"
255 #include "qgslayertreemapcanvasbridge.h"
256 #include "qgslayertreemodel.h"
257 #include "qgslayertreemodellegendnode.h"
258 #include "qgslayertreeregistrybridge.h"
259 #include "qgslayertreeutils.h"
260 #include "qgslayertreeview.h"
261 #include "qgslayertreeviewdefaultactions.h"
262 #include "qgslayertreeviewembeddedindicator.h"
263 #include "qgslayertreeviewfilterindicator.h"
264 #include "qgslayertreeviewlowaccuracyindicator.h"
265 #include "qgslayertreeviewmemoryindicator.h"
266 #include "qgslayertreeviewbadlayerindicator.h"
267 #include "qgslayertreeviewnonremovableindicator.h"
268 #include "qgslayertreeviewnotesindicator.h"
269 #include "qgslayertreeviewnocrsindicator.h"
270 #include "qgslayertreeviewtemporalindicator.h"
271 #include "qgslayertreeviewofflineindicator.h"
272 #include "qgsrasterpipe.h"
273 #include "qgslayout.h"
274 #include "qgslayoutatlas.h"
275 #include "qgslayoutcustomdrophandler.h"
276 #include "qgslayoutdesignerdialog.h"
277 #include "qgslayoutitemguiregistry.h"
278 #include "qgslayoutmanager.h"
279 #include "qgslayoutqptdrophandler.h"
280 #include "qgslayoutimagedrophandler.h"
281 #include "qgslayoutguiutils.h"
282 #include "qgslocatorwidget.h"
283 #include "qgslocator.h"
284 #include "qgsactionlocatorfilter.h"
285 #include "qgsactivelayerfeatureslocatorfilter.h"
286 #include "qgsalllayersfeatureslocatorfilter.h"
287 #include "qgsbookmarklocatorfilter.h"
288 #include "qgsexpressioncalculatorlocatorfilter.h"
289 #include "qgsgotolocatorfilter.h"
290 #include "qgslayertreelocatorfilter.h"
291 #include "qgslayoutlocatorfilter.h"
292 #include "qgsnominatimlocatorfilter.h"
293 #include "qgssettingslocatorfilter.h"
294 #include "qgsgeocoderlocatorfilter.h"
295 #include "qgsnominatimgeocoder.h"
296 #include "qgslogger.h"
297 #include "qgsmapcanvas.h"
298 #include "qgsmapcanvasdockwidget.h"
299 #include "qgsmapcanvassnappingutils.h"
300 #include "qgsmapcanvastracer.h"
301 #include "qgsmaplayer.h"
302 #include "qgsmaplayerstyleguiutils.h"
303 #include "qgsmapoverviewcanvas.h"
304 #include "qgsmapsettings.h"
305 #include "qgsmaptip.h"
306 #include "qgsmbtiles.h"
307 #include "qgsmenuheader.h"
308 #include "qgsmergeattributesdialog.h"
309 #include "qgsmessageviewer.h"
310 #include "qgsmessagebar.h"
311 #include "qgsmessagebaritem.h"
312 #include "qgsmeshlayer.h"
313 #include "qgsmeshlayerproperties.h"
314 #include "qgspointcloudlayer.h"
315 #include "qgsmemoryproviderutils.h"
316 #include "qgsmimedatautils.h"
317 #include "qgsmessagelog.h"
318 #include "qgsmultibandcolorrenderer.h"
319 #include "qgsnative.h"
320 #include "qgsnativealgorithms.h"
321 #include "qgsnewvectorlayerdialog.h"
322 #include "qgsnewmemorylayerdialog.h"
323 #include "qgsnewmeshlayerdialog.h"
324 #include "options/qgsoptions.h"
325 #include "qgspluginlayer.h"
326 #include "qgspluginlayerregistry.h"
327 #include "qgspluginmanager.h"
328 #include "qgspluginregistry.h"
329 #include "qgspointxy.h"
330 #include "qgspuzzlewidget.h"
331 #include "qgsruntimeprofiler.h"
332 #include "qgshandlebadlayers.h"
333 #include "qgsprintlayout.h"
334 #include "qgsprocessingregistry.h"
335 #include "qgsprojutils.h"
336 #include "qgsproject.h"
337 #include "qgsprojectlayergroupdialog.h"
338 #include "qgsprojectproperties.h"
339 #include "qgsprojectstorage.h"
340 #include "qgsprojectstorageguiprovider.h"
341 #include "qgsprojectstorageguiregistry.h"
342 #include "qgsprojectstorageregistry.h"
343 #include "qgsproviderregistry.h"
344 #include "qgsproviderguiregistry.h"
345 #include "qgspythonrunner.h"
346 #include "qgsproxyprogresstask.h"
347 #include "qgsquerybuilder.h"
348 #include "qgsrastercalcdialog.h"
349 #include "qgsmeshcalculatordialog.h"
350 #include "qgsrasterfilewriter.h"
351 #include "qgsrasterfilewritertask.h"
352 #include "qgsrasteriterator.h"
353 #include "qgsrasterlayer.h"
354 #include "qgsrasterlayerproperties.h"
355 #include "qgsrasternuller.h"
356 #include "qgsbrightnesscontrastfilter.h"
357 #include "qgsrasterrenderer.h"
358 #include "qgsrasterlayersaveasdialog.h"
359 #include "qgsrasterprojector.h"
360 #include "qgsreadwritecontext.h"
361 #include "qgsrectangle.h"
362 #include "qgsreport.h"
363 #include "qgsscalevisibilitydialog.h"
364 #include "qgsgroupwmsdatadialog.h"
365 #include "qgsselectbyformdialog.h"
366 #include "qgsshortcutsmanager.h"
367 #include "qgssinglebandgrayrenderer.h"
368 #include "qgssnappingwidget.h"
369 #include "qgsstatisticalsummarydockwidget.h"
370 #include "qgsstatusbar.h"
371 #include "qgsstatusbarcoordinateswidget.h"
372 #include "qgsstatusbarmagnifierwidget.h"
373 #include "qgsstatusbarscalewidget.h"
374 #include "qgsstyle.h"
375 #include "qgssubsetstringeditorproviderregistry.h"
376 #include "qgssubsetstringeditorprovider.h"
377 #include "qgssubsetstringeditorinterface.h"
378 #include "qgssvgannotation.h"
379 #include "qgstaskmanager.h"
380 #include "qgstaskmanagerwidget.h"
381 #include "qgssymbolselectordialog.h"
382 #include "qgstextannotation.h"
383 #include "qgsundowidget.h"
384 #include "qgsuserinputwidget.h"
385 #include "qgsvectordataprovider.h"
386 #include "qgsvectorfilewriter.h"
387 #include "qgsvectorlayer.h"
388 #include "qgsvectorlayerproperties.h"
389 #include "qgsvectorlayerdigitizingproperties.h"
390 #include "qgsvectortilelayer.h"
391 #include "qgsvectortilelayerproperties.h"
392 #include "qgspointcloudlayerproperties.h"
393 #include "qgsmapthemes.h"
394 #include "qgsmessagelogviewer.h"
395 #include "qgsdataitem.h"
396 #include "qgsmaplayeractionregistry.h"
397 #include "qgswelcomepage.h"
398 #include "qgsversioninfo.h"
399 #include "qgslegendfilterbutton.h"
400 #include "qgsvirtuallayerdefinition.h"
401 #include "qgsvirtuallayerdefinitionutils.h"
402 #include "qgstransaction.h"
403 #include "qgstransactiongroup.h"
404 #include "qgsvectorlayerjoininfo.h"
405 #include "qgsvectorlayerutils.h"
406 #include "qgshelp.h"
407 #include "qgsvectorfilewritertask.h"
408 #include "qgsmapsavedialog.h"
409 #include "qgsmaprenderertask.h"
410 #include "qgsmapdecoration.h"
411 #include "qgsnewnamedialog.h"
412 #include "qgsgui.h"
413 #include "qgsdatasourcemanagerdialog.h"
414 #include "qgsappwindowmanager.h"
415 #include "qgsvaliditycheckregistry.h"
416 #include "qgsappcoordinateoperationhandlers.h"
417 #include "qgsprojectviewsettings.h"
418 #include "qgscoordinateformatter.h"
419 #include "qgslocaldefaultsettings.h"
420 #include "qgsbearingnumericformat.h"
421 #include "qgsprojectdisplaysettings.h"
422 #include "qgstemporalcontrollerdockwidget.h"
423 #include "qgsnetworklogger.h"
424 #include "qgsuserprofilemanager.h"
425 #include "qgsuserprofile.h"
426 #include "qgsnetworkloggerwidgetfactory.h"
427 #include "devtools/profiler/qgsprofilerwidgetfactory.h"
428 #include "qgsabstractdatabaseproviderconnection.h"
429 #include "qgszipitem.h"
430 
431 #include "browser/qgsinbuiltdataitemproviders.h"
432 
433 #include "qgssublayersdialog.h"
434 #include "ogr/qgsvectorlayersaveasdialog.h"
435 #include "qgsannotationitemguiregistry.h"
436 #include "annotations/qgsannotationlayerproperties.h"
437 #include "qgscreateannotationitemmaptool.h"
438 
439 #include "pointcloud/qgspointcloudelevationpropertieswidget.h"
440 #include "pointcloud/qgspointcloudlayerstylewidget.h"
441 
442 #ifdef ENABLE_MODELTEST
443 #include "modeltest.h"
444 #endif
445 
446 //
447 // GDAL/OGR includes
448 //
449 #include <ogr_api.h>
450 #include <gdal_version.h>
451 #include <proj.h>
452 
453 #ifdef HAVE_PDAL
454 #include <pdal/pdal.hpp>
455 #endif
456 
457 //
458 // Other includes
459 //
460 #include <algorithm>
461 #include <cassert>
462 #include <cmath>
463 #include <functional>
464 #include <iomanip>
465 #include <list>
466 #include <memory>
467 #include <vector>
468 
469 #include "qgsmeasuretool.h"
470 #include "qgsmapcanvasannotationitem.h"
471 #include "qgsmaptoolpan.h"
472 #include "qgsmaptoolidentifyaction.h"
473 #include "qgsmaptoolpinlabels.h"
474 #include "qgsmaptoolmeasureangle.h"
475 #include "qgsmaptoolmeasurebearing.h"
476 #include "qgsmaptoolrotatepointsymbols.h"
477 #include "qgsmaptooldigitizefeature.h"
478 #include "qgsmaptooloffsetpointsymbol.h"
479 #include "vertextool/qgsvertextool.h"
480 #include "qgsmaptooleditmeshframe.h"
481 
482 #include "qgsgeometryvalidationmodel.h"
483 #include "qgsgeometryvalidationdock.h"
484 #include "qgslayoutvaliditychecks.h"
485 
486 // Editor widgets
487 #include "qgseditorwidgetregistry.h"
488 //
489 // Conditional Includes
490 //
491 #ifdef HAVE_PGCONFIG
492 #undef PACKAGE_BUGREPORT
493 #undef PACKAGE_NAME
494 #undef PACKAGE_STRING
495 #undef PACKAGE_TARNAME
496 #undef PACKAGE_VERSION
497 #include <pg_config.h>
498 #else
499 #define PG_VERSION "unknown"
500 #endif
501 
502 #include <sqlite3.h>
503 
504 #ifdef HAVE_SPATIALITE
505 extern "C"
506 {
507 #include <spatialite.h>
508 }
509 #include "qgsnewspatialitelayerdialog.h"
510 #endif
511 
512 #include "qgsnewgeopackagelayerdialog.h"
513 
514 #ifdef WITH_BINDINGS
515 #include "qgspythonutils.h"
516 #endif
517 
518 #ifndef Q_OS_WIN
519 #include <dlfcn.h>
520 #else
521 #include <shellapi.h>
522 #include <dbghelp.h>
523 #endif
524 
525 class QTreeWidgetItem;
526 class QgsUserProfileManager;
527 class QgsUserProfile;
528 
529 /**
530  * Set the application title bar text
531  */
setTitleBarText_(QWidget & qgisApp)532 static void setTitleBarText_( QWidget &qgisApp )
533 {
534   QString caption;
535   if ( QgsProject::instance()->title().isEmpty() )
536   {
537     if ( QgsProject::instance()->fileName().isEmpty() )
538     {
539       // new project
540       caption = QgisApp::tr( "Untitled Project" );
541     }
542     else
543     {
544       caption = QgsProject::instance()->baseName();
545     }
546   }
547   else
548   {
549     caption = QgsProject::instance()->title();
550   }
551   if ( !caption.isEmpty() )
552   {
553     caption += QStringLiteral( " %1 " ).arg( QChar( 0x2014 ) );
554   }
555   if ( QgsProject::instance()->isDirty() )
556     caption.prepend( '*' );
557 
558   caption += QgisApp::tr( "QGIS" );
559 
560   if ( Qgis::version().endsWith( QLatin1String( "Master" ) ) )
561   {
562     caption += QStringLiteral( " %1" ).arg( Qgis::devVersion() );
563   }
564 
565   if ( QgisApp::instance()->userProfileManager()->allProfiles().count() > 1 )
566   {
567     // add current profile (if it's not the default one)
568     QgsUserProfile *profile = QgisApp::instance()->userProfileManager()->userProfile();
569     if ( profile->name() != QLatin1String( "default" ) )
570       caption += QStringLiteral( " [%1]" ).arg( profile->name() );
571   }
572 
573   qgisApp.setWindowTitle( caption );
574 }
575 
576 /**
577  * Creator function for output viewer
578 */
messageOutputViewer_()579 static QgsMessageOutput *messageOutputViewer_()
580 {
581   if ( QThread::currentThread() == qApp->thread() )
582     return new QgsMessageViewer( QgisApp::instance() );
583   else
584     return new QgsMessageOutputConsole();
585 }
586 
customSrsValidation_(QgsCoordinateReferenceSystem & srs)587 static void customSrsValidation_( QgsCoordinateReferenceSystem &srs )
588 {
589   const QgsOptions::UnknownLayerCrsBehavior mode = QgsSettings().enumValue( QStringLiteral( "/projections/unknownCrsBehavior" ), QgsOptions::UnknownLayerCrsBehavior::NoAction, QgsSettings::App );
590   switch ( mode )
591   {
592     case QgsOptions::UnknownLayerCrsBehavior::NoAction:
593       return;
594 
595     case QgsOptions::UnknownLayerCrsBehavior::UseDefaultCrs:
596       srs.createFromOgcWmsCrs( QgsSettings().value( QStringLiteral( "Projections/layerDefaultCrs" ), geoEpsgCrsAuthId() ).toString() );
597       break;
598 
599     case QgsOptions::UnknownLayerCrsBehavior::PromptUserForCrs:
600     case QgsOptions::UnknownLayerCrsBehavior::UseProjectCrs:
601       // can't take any action immediately for these -- we may be in a background thread
602       break;
603   }
604 
605   if ( QThread::currentThread() != QApplication::instance()->thread() )
606   {
607     // Running in a background thread -- we can't queue this connection, because
608     // srs is a reference and may be deleted before the queued slot is called.
609     // We also can't do ANY gui related stuff here. Best we can do is log
610     // a warning and move on...
611     QgsMessageLog::logMessage( QObject::tr( "Layer has unknown CRS" ) );
612   }
613   else
614   {
615     QgisApp::instance()->emitCustomCrsValidation( srs );
616   }
617 }
618 
emitCustomCrsValidation(QgsCoordinateReferenceSystem & srs)619 void QgisApp::emitCustomCrsValidation( QgsCoordinateReferenceSystem &srs )
620 {
621   emit customCrsValidation( srs );
622 }
623 
layerTreeViewDoubleClicked(const QModelIndex & index)624 void QgisApp::layerTreeViewDoubleClicked( const QModelIndex &index )
625 {
626   Q_UNUSED( index )
627   QgsSettings settings;
628   switch ( settings.value( QStringLiteral( "qgis/legendDoubleClickAction" ), 0 ).toInt() )
629   {
630     case 0:
631     {
632       //show properties
633       if ( mLayerTreeView )
634       {
635         // if it's a legend node, open symbol editor directly
636         if ( QgsSymbolLegendNode *node = qobject_cast<QgsSymbolLegendNode *>( mLayerTreeView->currentLegendNode() ) )
637         {
638           const QgsSymbol *originalSymbol = node->symbol();
639           if ( !originalSymbol )
640             return;
641 
642           std::unique_ptr< QgsSymbol > symbol( originalSymbol->clone() );
643           QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( node->layerNode()->layer() );
644           QgsSymbolSelectorDialog dlg( symbol.get(), QgsStyle::defaultStyle(), vlayer, this );
645           QgsSymbolWidgetContext context;
646           context.setMapCanvas( mMapCanvas );
647           context.setMessageBar( mInfoBar );
648           dlg.setContext( context );
649           if ( dlg.exec() )
650           {
651             node->setSymbol( symbol.release() );
652           }
653 
654           return;
655         }
656       }
657       QgisApp::instance()->layerProperties();
658       break;
659     }
660     case 1:
661     {
662       QgsSettings settings;
663       QgsAttributeTableFilterModel::FilterMode initialMode = settings.enumValue( QStringLiteral( "qgis/attributeTableBehavior" ),  QgsAttributeTableFilterModel::ShowAll );
664       QgisApp::instance()->attributeTable( initialMode );
665       break;
666     }
667     case 2:
668       mapStyleDock( true );
669       break;
670     default:
671       break;
672   }
673 }
674 
onActiveLayerChanged(QgsMapLayer * layer)675 void QgisApp::onActiveLayerChanged( QgsMapLayer *layer )
676 {
677   if ( mBlockActiveLayerChanged )
678     return;
679 
680   const QList< QgsMapCanvas * > canvases = mapCanvases();
681   for ( QgsMapCanvas *canvas : canvases )
682     canvas->setCurrentLayer( layer );
683 
684   if ( mUndoWidget )
685   {
686     if ( layer )
687     {
688       mUndoWidget->setUndoStack( layer->undoStack() );
689     }
690     else
691     {
692       mUndoWidget->unsetStack();
693     }
694     updateUndoActions();
695   }
696 
697   emit activeLayerChanged( layer );
698 }
699 
vectorLayerStyleLoaded(QgsVectorLayer * vl,QgsMapLayer::StyleCategories categories)700 void QgisApp::vectorLayerStyleLoaded( QgsVectorLayer *vl, QgsMapLayer::StyleCategories categories )
701 {
702   if ( vl && vl->isValid( ) )
703   {
704 
705     // Check broken dependencies in forms
706     if ( categories.testFlag( QgsMapLayer::StyleCategory::Forms ) )
707     {
708       resolveVectorLayerDependencies( vl );
709     }
710 
711     // Check broken relations and try to restore them
712     if ( categories.testFlag( QgsMapLayer::StyleCategory::Relations ) )
713     {
714       resolveVectorLayerWeakRelations( vl );
715     }
716 
717   }
718 }
719 
toggleEventTracing()720 void QgisApp::toggleEventTracing()
721 {
722   QgsSettings settings;
723   if ( !settings.value( QStringLiteral( "qgis/enableEventTracing" ), false ).toBool() )
724   {
725     // make sure the setting is available in Options > Advanced
726     if ( !settings.contains( QStringLiteral( "qgis/enableEventTracing" ) ) )
727       settings.setValue( QStringLiteral( "qgis/enableEventTracing" ), false );
728 
729     messageBar()->pushWarning( tr( "Event Tracing" ), tr( "Tracing is not enabled. Look for \"enableEventTracing\" in Options > Advanced." ) );
730     return;
731   }
732 
733   if ( !QgsEventTracing::isTracingEnabled() )
734   {
735     messageBar()->pushSuccess( tr( "Event Tracing" ), tr( "Tracing started." ) );
736     QgsEventTracing::startTracing();
737   }
738   else
739   {
740     QgsEventTracing::stopTracing();
741     QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Event Trace..." ), QString(), tr( "Event Traces (*.json)" ) );
742     if ( !fileName.isEmpty() )
743       QgsEventTracing::writeTrace( fileName );
744   }
745 }
746 
747 #ifdef HAVE_GEOREFERENCER
showGeoreferencer()748 void QgisApp::showGeoreferencer()
749 {
750   if ( !mGeoreferencer )
751     mGeoreferencer = new QgsGeoreferencerMainWindow( this );
752   mGeoreferencer->show();
753   mGeoreferencer->setFocus();
754 }
755 #endif
756 
annotationItemTypeAdded(int id)757 void QgisApp::annotationItemTypeAdded( int id )
758 {
759   if ( QgsGui::annotationItemGuiRegistry()->itemMetadata( id )->flags() & Qgis::AnnotationItemGuiFlag::FlagNoCreationTools )
760     return;
761 
762   QString name = QgsGui::annotationItemGuiRegistry()->itemMetadata( id )->visibleName();
763   QString groupId = QgsGui::annotationItemGuiRegistry()->itemMetadata( id )->groupId();
764   QToolButton *groupButton = nullptr;
765   if ( !groupId.isEmpty() )
766   {
767     // find existing group toolbutton and submenu, or create new ones if this is the first time the group has been encountered
768     const QgsAnnotationItemGuiGroup &group = QgsGui::annotationItemGuiRegistry()->itemGroup( groupId );
769     QIcon groupIcon = group.icon.isNull() ? QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicShape.svg" ) ) : group.icon;
770     QString groupText = tr( "Create %1" ).arg( group.name );
771     if ( mAnnotationItemGroupToolButtons.contains( groupId ) )
772     {
773       groupButton = mAnnotationItemGroupToolButtons.value( groupId );
774     }
775     else
776     {
777       QToolButton *groupToolButton = new QToolButton( mAnnotationsToolBar );
778       groupToolButton->setIcon( groupIcon );
779       groupToolButton->setCheckable( true );
780       groupToolButton->setPopupMode( QToolButton::InstantPopup );
781       groupToolButton->setAutoRaise( true );
782       groupToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
783       groupToolButton->setToolTip( groupText );
784       mAnnotationsToolBar->insertWidget( mAnnotationsItemInsertBefore, groupToolButton );
785       mAnnotationItemGroupToolButtons.insert( groupId, groupToolButton );
786       groupButton = groupToolButton;
787     }
788   }
789 
790   // update UI for new item type
791   QAction *action = new QAction( tr( "Create %1" ).arg( name ), this );
792   action->setToolTip( tr( "Create %1" ).arg( name ) );
793   action->setCheckable( true );
794   action->setData( id );
795   action->setIcon( QgsGui::annotationItemGuiRegistry()->itemMetadata( id )->creationIcon() );
796 
797   mMapToolGroup->addAction( action );
798 
799   if ( groupButton )
800     groupButton->addAction( action );
801   else
802   {
803     mAnnotationsToolBar->insertAction( mAnnotationsItemInsertBefore, action );
804   }
805 
806   connect( action, &QAction::toggled, this, [this, action, id]( bool checked )
807   {
808     if ( !checked )
809       return;
810 
811     QgsCreateAnnotationItemMapToolInterface *tool = QgsGui::annotationItemGuiRegistry()->itemMetadata( id )->createMapTool( mMapCanvas, mAdvancedDigitizingDockWidget );
812     tool->mapTool()->setAction( action );
813     mMapCanvas->setMapTool( tool->mapTool() );
814     if ( qobject_cast< QgsMapToolCapture * >( tool->mapTool() ) )
815     {
816       enableDigitizeTechniqueActions( checked, action );
817     }
818 
819     connect( tool->mapTool(), &QgsMapTool::deactivated, tool->mapTool(), &QObject::deleteLater );
820     connect( tool->handler(), &QgsCreateAnnotationItemMapToolHandler::itemCreated, this, [ = ]
821     {
822       QgsAnnotationItem *item = tool->handler()->takeCreatedItem();
823       QgsAnnotationLayer *targetLayer = qobject_cast< QgsAnnotationLayer * >( activeLayer() );
824       if ( !targetLayer )
825         targetLayer = QgsProject::instance()->mainAnnotationLayer();
826 
827       const QString itemId = targetLayer->addItem( item );
828       // automatically select item in layer styling panel
829       mMapStyleWidget->setAnnotationItem( targetLayer, itemId );
830       mMapStylingDock->setUserVisible( true );
831       mMapStyleWidget->focusDefaultWidget();
832 
833       QgsProject::instance()->setDirty( true );
834 
835       // TODO -- possibly automatically deactivate the tool now?
836     } );
837   } );
838 }
839 
840 /*
841  * This function contains forced validation of CRS used in QGIS.
842  * There are 4 options depending on the settings:
843  * - ask for CRS using projection selecter
844  * - use project's CRS
845  * - use predefined global CRS
846  * - take no action (leave as unknown CRS)
847  */
validateCrs(QgsCoordinateReferenceSystem & srs)848 void QgisApp::validateCrs( QgsCoordinateReferenceSystem &srs )
849 {
850   static QString sAuthId = QString();
851 
852   const QgsOptions::UnknownLayerCrsBehavior mode = QgsSettings().enumValue( QStringLiteral( "/projections/unknownCrsBehavior" ), QgsOptions::UnknownLayerCrsBehavior::NoAction, QgsSettings::App );
853   switch ( mode )
854   {
855     case QgsOptions::UnknownLayerCrsBehavior::NoAction:
856       break;
857 
858     case QgsOptions::UnknownLayerCrsBehavior::UseDefaultCrs:
859     {
860       srs.createFromOgcWmsCrs( QgsSettings().value( QStringLiteral( "Projections/layerDefaultCrs" ), geoEpsgCrsAuthId() ).toString() );
861       sAuthId = srs.authid();
862       visibleMessageBar()->pushMessage( tr( "CRS was undefined" ), tr( "defaulting to CRS %1" ).arg( srs.userFriendlyIdentifier() ), Qgis::MessageLevel::Warning );
863       break;
864     }
865 
866     case QgsOptions::UnknownLayerCrsBehavior::PromptUserForCrs:
867     {
868       // \note this class is not a descendent of QWidget so we can't pass
869       // it in the ctor of the layer projection selector
870 
871       static bool opening = false;
872       if ( opening )
873         break;
874       opening = true;
875 
876       QgsProjectionSelectionDialog *mySelector = new QgsProjectionSelectionDialog();
877       const QString validationHint = srs.validationHint();
878       if ( !validationHint.isEmpty() )
879         mySelector->setMessage( validationHint );
880       else
881         mySelector->showNoCrsForLayerMessage();
882 
883       if ( sAuthId.isNull() )
884         sAuthId = QgsProject::instance()->crs().authid();
885 
886       QgsCoordinateReferenceSystem defaultCrs( sAuthId );
887       if ( defaultCrs.isValid() )
888       {
889         mySelector->setCrs( defaultCrs );
890       }
891 
892       QgsTemporaryCursorRestoreOverride cursorOverride;
893 
894       if ( mySelector->exec() )
895       {
896         QgsDebugMsgLevel( "Layer srs set from dialog: " + QString::number( mySelector->crs().srsid() ), 2 );
897         srs = mySelector->crs();
898         sAuthId = srs.authid();
899       }
900 
901       delete mySelector;
902       opening = false;
903       break;
904     }
905 
906     case QgsOptions::UnknownLayerCrsBehavior::UseProjectCrs:
907     {
908       // XXX TODO: Change project to store selected CS as 'projectCRS' not 'selectedWkt'
909       srs = QgsProject::instance()->crs();
910       sAuthId = srs.authid();
911       QgsDebugMsgLevel( "Layer srs set from project: " + sAuthId, 2 );
912       visibleMessageBar()->pushMessage( tr( "CRS was undefined" ), tr( "defaulting to project CRS %1" ).arg( srs.userFriendlyIdentifier() ), Qgis::MessageLevel::Warning );
913       break;
914     }
915   }
916 }
917 
918 
cmpByText_(QAction * a,QAction * b)919 static bool cmpByText_( QAction *a, QAction *b )
920 {
921   return QString::localeAwareCompare( a->text(), b->text() ) < 0;
922 }
923 
924 
925 QgisApp *QgisApp::sInstance = nullptr;
926 
927 // constructor starts here
QgisApp(QSplashScreen * splash,bool restorePlugins,bool skipVersionCheck,const QString & rootProfileLocation,const QString & activeProfile,QWidget * parent,Qt::WindowFlags fl)928 QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCheck, const QString &rootProfileLocation, const QString &activeProfile, QWidget *parent, Qt::WindowFlags fl )
929   : QMainWindow( parent, fl )
930   , mSplash( splash )
931 {
932   if ( sInstance )
933   {
934     QMessageBox::critical(
935       this,
936       tr( "Multiple Instances of QgisApp" ),
937       tr( "Multiple instances of QGIS application object detected.\nPlease contact the developers.\n" ) );
938     abort();
939   }
940 
941   sInstance = this;
942   QgsRuntimeProfiler *profiler = QgsApplication::profiler();
943 
944   QColor splashTextColor = Qgis::releaseName() == QLatin1String( "Master" ) ? QColor( 93, 153, 51 ) : Qt::black;
945 
946   startProfile( tr( "Create user profile manager" ) );
947   mUserProfileManager = new QgsUserProfileManager( QString(), this );
948   mUserProfileManager->setRootLocation( rootProfileLocation );
949   mUserProfileManager->setActiveUserProfile( activeProfile );
950   mUserProfileManager->setNewProfileNotificationEnabled( true );
951   connect( mUserProfileManager, &QgsUserProfileManager::profilesChanged, this, &QgisApp::refreshProfileMenu );
952   endProfile();
953 
954   // start the network logger early, we want all requests logged!
955   startProfile( tr( "Create network logger" ) );
956   mNetworkLogger = new QgsNetworkLogger( QgsNetworkAccessManager::instance(), this );
957   endProfile();
958 
959   // load GUI: actions, menus, toolbars
960   startProfile( tr( "Setting up UI" ) );
961   setupUi( this );
962   // because mActionToggleMapOnly can hide the menu (thereby disabling menu actions),
963   //  we attach the following actions to the MainWindow too (to be able to come back)
964   this->addAction( mActionToggleFullScreen );
965   this->addAction( mActionTogglePanelsVisibility );
966   this->addAction( mActionToggleMapOnly );
967   endProfile();
968 
969   setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
970 
971   //////////
972 
973   startProfile( tr( "Checking user database" ) );
974   mSplash->showMessage( tr( "Checking database" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
975   qApp->processEvents();
976   // Do this early on before anyone else opens it and prevents us copying it
977   QString dbError;
978   if ( !QgsApplication::createDatabase( &dbError ) )
979   {
980     QMessageBox::critical( this, tr( "Private qgis.db" ), dbError );
981   }
982   endProfile();
983 
984   // Create the themes folder for the user
985   startProfile( tr( "Creating theme folder" ) );
986   QgsApplication::createThemeFolder();
987   endProfile();
988 
989   mSplash->showMessage( tr( "Reading settings" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
990   qApp->processEvents();
991 
992   mSplash->showMessage( tr( "Setting up the GUI" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
993   qApp->processEvents();
994 
995   QgsApplication::initQgis();
996   if ( !QgsApplication::authManager()->isDisabled() )
997   {
998     // Most of the auth initialization is now done inside initQgis, no need to profile here
999     masterPasswordSetup();
1000   }
1001 
1002   QgsSettings settings;
1003 
1004 
1005   startProfile( tr( "Building style sheet" ) );
1006   // set up stylesheet builder and apply saved or default style options
1007   mStyleSheetBuilder = new QgisAppStyleSheet( this );
1008   connect( mStyleSheetBuilder, &QgisAppStyleSheet::appStyleSheetChanged,
1009            this, &QgisApp::setAppStyleSheet );
1010   endProfile();
1011 
1012   QWidget *centralWidget = this->centralWidget();
1013   QGridLayout *centralLayout = new QGridLayout( centralWidget );
1014   centralWidget->setLayout( centralLayout );
1015   centralLayout->setContentsMargins( 0, 0, 0, 0 );
1016 
1017   // "theMapCanvas" used to find this canonical instance later
1018   startProfile( tr( "Creating map canvas" ) );
1019   mMapCanvas = new QgsMapCanvas( centralWidget );
1020   mMapCanvas->setObjectName( QStringLiteral( "theMapCanvas" ) );
1021 
1022   // before anything, let's freeze canvas redraws
1023   QgsCanvasRefreshBlocker refreshBlocker;
1024 
1025   connect( mMapCanvas, &QgsMapCanvas::messageEmitted, this, &QgisApp::displayMessage );
1026 
1027   if ( settings.value( QStringLiteral( "qgis/main_canvas_preview_jobs" ) ).isNull() )
1028   {
1029     // So that it appears in advanced settings
1030     settings.setValue( QStringLiteral( "qgis/main_canvas_preview_jobs" ), true );
1031   }
1032   mMapCanvas->setPreviewJobsEnabled( settings.value( QStringLiteral( "qgis/main_canvas_preview_jobs" ), true ).toBool() );
1033 
1034   // set canvas color right away
1035   int myRed = settings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
1036   int myGreen = settings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
1037   int myBlue = settings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
1038   mMapCanvas->setCanvasColor( QColor( myRed, myGreen, myBlue ) );
1039 
1040   // set project linked to main canvas
1041   mMapCanvas->setProject( QgsProject::instance() );
1042   endProfile();
1043 
1044   // what type of project to auto-open
1045   mProjOpen = settings.value( QStringLiteral( "qgis/projOpenAtLaunch" ), 0 ).toInt();
1046 
1047   // a bar to warn the user with non-blocking messages
1048   startProfile( tr( "Message bar" ) );
1049   mInfoBar = new QgsMessageBar( centralWidget );
1050   mInfoBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
1051   centralLayout->addWidget( mInfoBar, 0, 0, 1, 1 );
1052   endProfile();
1053 
1054   startProfile( tr( "Welcome page" ) );
1055   mWelcomePage = new QgsWelcomePage( skipVersionCheck );
1056   connect( mWelcomePage, &QgsWelcomePage::projectRemoved, this, [ this ]( int row )
1057   {
1058     mRecentProjects.removeAt( row );
1059     saveRecentProjects();
1060     updateRecentProjectPaths();
1061   } );
1062   connect( mWelcomePage, &QgsWelcomePage::projectPinned, this, [ this ]( int row )
1063   {
1064     mRecentProjects.at( row ).pin = true;
1065     saveRecentProjects();
1066     updateRecentProjectPaths();
1067   } );
1068   connect( mWelcomePage, &QgsWelcomePage::projectUnpinned, this, [ this ]( int row )
1069   {
1070     mRecentProjects.at( row ).pin = false;
1071     saveRecentProjects();
1072     updateRecentProjectPaths();
1073   } );
1074   endProfile();
1075 
1076   mCentralContainer = new QStackedWidget;
1077   mCentralContainer->insertWidget( 0, mMapCanvas );
1078   mCentralContainer->insertWidget( 1, mWelcomePage );
1079 
1080   centralLayout->addWidget( mCentralContainer, 0, 0, 2, 1 );
1081   mInfoBar->raise();
1082 
1083   connect( mMapCanvas, &QgsMapCanvas::layersChanged, this, &QgisApp::showMapCanvas );
1084 
1085   mCentralContainer->setCurrentIndex( mProjOpen ? 0 : 1 );
1086 
1087   startProfile( tr( "User input dock" ) );
1088   // User Input Dock Widget
1089   mUserInputDockWidget = new QgsUserInputWidget( mMapCanvas );
1090   mUserInputDockWidget->setObjectName( QStringLiteral( "UserInputDockWidget" ) );
1091   mUserInputDockWidget->setAnchorWidget( mMapCanvas );
1092   mUserInputDockWidget->setAnchorWidgetPoint( QgsFloatingWidget::TopRight );
1093   mUserInputDockWidget->setAnchorPoint( QgsFloatingWidget::TopRight );
1094 
1095   endProfile();
1096 
1097   //set the focus to the map canvas
1098   mMapCanvas->setFocus();
1099 
1100   startProfile( tr( "Layer tree" ) );
1101   mLayerTreeView = new QgsLayerTreeView( this );
1102   mLayerTreeView->setObjectName( QStringLiteral( "theLayerTreeView" ) ); // "theLayerTreeView" used to find this canonical instance later
1103   endProfile();
1104 
1105   // create undo widget
1106   startProfile( tr( "Undo dock" ) );
1107   mUndoDock = new QgsDockWidget( tr( "Undo/Redo" ), this );
1108   QShortcut *showUndoDock = new QShortcut( QKeySequence( tr( "Ctrl+5" ) ), this );
1109   connect( showUndoDock, &QShortcut::activated, mUndoDock, &QgsDockWidget::toggleUserVisible );
1110   showUndoDock->setObjectName( QStringLiteral( "ShowUndoPanel" ) );
1111   showUndoDock->setWhatsThis( tr( "Show Undo/Redo Panel" ) );
1112 
1113   mUndoWidget = new QgsUndoWidget( mUndoDock, mMapCanvas );
1114   mUndoWidget->setObjectName( QStringLiteral( "Undo" ) );
1115   mUndoDock->setWidget( mUndoWidget );
1116   mUndoDock->setObjectName( QStringLiteral( "undo/redo dock" ) );
1117   endProfile();
1118 
1119   // Advanced Digitizing dock
1120   startProfile( tr( "Advanced digitize panel" ) );
1121   mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this );
1122   mAdvancedDigitizingDockWidget->setWindowTitle( tr( "Advanced Digitizing" ) );
1123   mAdvancedDigitizingDockWidget->setObjectName( QStringLiteral( "AdvancedDigitizingTools" ) );
1124 
1125   QShortcut *showAdvancedDigitizingDock = new QShortcut( QKeySequence( tr( "Ctrl+4" ) ), this );
1126   connect( showAdvancedDigitizingDock, &QShortcut::activated, mAdvancedDigitizingDockWidget, &QgsDockWidget::toggleUserVisible );
1127   showAdvancedDigitizingDock->setObjectName( QStringLiteral( "ShowAdvancedDigitizingPanel" ) );
1128   showAdvancedDigitizingDock->setWhatsThis( tr( "Show Advanced Digitizing Panel" ) );
1129 
1130   endProfile();
1131 
1132   // Statistical Summary dock
1133   startProfile( tr( "Statistics dock" ) );
1134   mStatisticalSummaryDockWidget = new QgsStatisticalSummaryDockWidget( this );
1135   mStatisticalSummaryDockWidget->setObjectName( QStringLiteral( "StatisticalSummaryDockWidget" ) );
1136 
1137   QShortcut *showStatsDock = new QShortcut( QKeySequence( tr( "Ctrl+6" ) ), this );
1138   connect( showStatsDock, &QShortcut::activated, mStatisticalSummaryDockWidget, &QgsDockWidget::toggleUserVisible );
1139   showStatsDock->setObjectName( QStringLiteral( "ShowStatisticsPanel" ) );
1140   showStatsDock->setWhatsThis( tr( "Show Statistics Panel" ) );
1141 
1142   endProfile();
1143 
1144   // Bookmarks dock
1145   startProfile( tr( "Bookmarks widget" ) );
1146   mBookMarksDockWidget = new QgsBookmarks( this );
1147   mBookMarksDockWidget->setObjectName( QStringLiteral( "BookmarksDockWidget" ) );
1148 
1149   QShortcut *showBookmarksDock = new QShortcut( QKeySequence( tr( "Ctrl+7" ) ), this );
1150   connect( showBookmarksDock, &QShortcut::activated, mBookMarksDockWidget, &QgsDockWidget::toggleUserVisible );
1151   showBookmarksDock->setObjectName( QStringLiteral( "ShowBookmarksPanel" ) );
1152   showBookmarksDock->setWhatsThis( tr( "Show Bookmarks Panel" ) );
1153   mBookMarksDockWidget->setToggleVisibilityAction( mActionShowBookmarkManager );
1154 
1155   connect( mActionShowBookmarks, &QAction::triggered, this, [ = ] { showBookmarks(); } );
1156 
1157   endProfile();
1158 
1159   startProfile( tr( "Snapping utilities" ) );
1160   mSnappingUtils = new QgsMapCanvasSnappingUtils( mMapCanvas, this );
1161   mMapCanvas->setSnappingUtils( mSnappingUtils );
1162   connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, mSnappingUtils, &QgsSnappingUtils::setConfig );
1163 
1164   endProfile();
1165 
1166   functionProfile( &QgisApp::createMenus, this, QStringLiteral( "Create menus" ) );
1167   functionProfile( &QgisApp::createActions, this, QStringLiteral( "Create actions" ) );
1168   functionProfile( &QgisApp::createActionGroups, this, QStringLiteral( "Create action group" ) );
1169 
1170   // create tools
1171   mMapTools = std::make_unique< QgsAppMapTools >( mMapCanvas, mAdvancedDigitizingDockWidget );
1172 
1173   functionProfile( &QgisApp::createToolBars, this, QStringLiteral( "Toolbars" ) );
1174   functionProfile( &QgisApp::createStatusBar, this, QStringLiteral( "Status bar" ) );
1175   functionProfile( &QgisApp::setupCanvasTools, this, QStringLiteral( "Create canvas tools" ) );
1176   const QList< QgsMapToolCapture * > captureTools = mMapTools->captureTools();
1177   for ( QgsMapToolCapture *tool : captureTools )
1178   {
1179     connect( tool->action(), &QAction::toggled, this, [this, tool]( bool checked ) { enableDigitizeTechniqueActions( checked, tool->action() ); } );
1180   }
1181 
1182   applyDefaultSettingsToCanvas( mMapCanvas );
1183 
1184   functionProfile( &QgisApp::initLayerTreeView, this, QStringLiteral( "Initialize layer tree view" ) );
1185   functionProfile( &QgisApp::createOverview, this, QStringLiteral( "Create overview" ) );
1186   functionProfile( &QgisApp::createMapTips, this, QStringLiteral( "Create map tips" ) );
1187   functionProfile( &QgisApp::createDecorations, this, QStringLiteral( "Create decorations" ) );
1188   functionProfile( &QgisApp::readSettings, this, QStringLiteral( "Read settings" ) );
1189   functionProfile( &QgisApp::updateProjectFromTemplates, this, QStringLiteral( "Update project from templates" ) );
1190   functionProfile( &QgisApp::legendLayerSelectionChanged, this, QStringLiteral( "Legend layer selection changed" ) );
1191   functionProfile( &QgisApp::init3D, this, QStringLiteral( "Initialize 3D support" ) );
1192   functionProfile( &QgisApp::initNativeProcessing, this, QStringLiteral( "Initialize native processing" ) );
1193   functionProfile( &QgisApp::initLayouts, this, QStringLiteral( "Initialize layouts support" ) );
1194 
1195   startProfile( tr( "Geometry validation" ) );
1196 
1197   mGeometryValidationService = std::make_unique<QgsGeometryValidationService>( QgsProject::instance() );
1198   mGeometryValidationService->setMessageBar( mInfoBar );
1199   mGeometryValidationDock = new QgsGeometryValidationDock( tr( "Geometry Validation" ), mMapCanvas, this );
1200   mGeometryValidationDock->hide();
1201   mGeometryValidationModel = new QgsGeometryValidationModel( mGeometryValidationService.get(), mGeometryValidationDock );
1202   connect( this, &QgisApp::activeLayerChanged, mGeometryValidationModel, [this]( QgsMapLayer * layer )
1203   {
1204     mGeometryValidationModel->setCurrentLayer( qobject_cast<QgsVectorLayer *>( layer ) );
1205   } );
1206   mGeometryValidationDock->setGeometryValidationModel( mGeometryValidationModel );
1207   mGeometryValidationDock->setGeometryValidationService( mGeometryValidationService.get() );
1208   endProfile();
1209 
1210   QgsApplication::annotationRegistry()->addAnnotationType( QgsAnnotationMetadata( QStringLiteral( "FormAnnotationItem" ), &QgsFormAnnotation::create ) );
1211   connect( QgsProject::instance()->annotationManager(), &QgsAnnotationManager::annotationAdded, this, &QgisApp::annotationCreated );
1212 
1213   mSaveRollbackInProgress = false;
1214 
1215   QString templateDirName = settings.value( QStringLiteral( "qgis/projectTemplateDir" ),
1216                             QString( QgsApplication::qgisSettingsDirPath() + "project_templates" ) ).toString();
1217   if ( !QFileInfo::exists( templateDirName ) )
1218   {
1219     // create default template directory
1220     if ( !QDir().mkdir( QgsApplication::qgisSettingsDirPath() + "project_templates" ) )
1221       templateDirName.clear();
1222   }
1223   if ( !templateDirName.isEmpty() ) // template directory exists, so watch it!
1224   {
1225     QFileSystemWatcher *projectsTemplateWatcher = new QFileSystemWatcher( this );
1226     projectsTemplateWatcher->addPath( templateDirName );
1227     connect( projectsTemplateWatcher, &QFileSystemWatcher::directoryChanged, this, [this] { updateProjectFromTemplates(); } );
1228   }
1229 
1230   // initialize the plugin manager
1231   startProfile( tr( "Plugin manager" ) );
1232   mPluginManager = new QgsPluginManager( this, restorePlugins );
1233   endProfile();
1234 
1235   addDockWidget( Qt::LeftDockWidgetArea, mUndoDock );
1236   mUndoDock->hide();
1237 
1238   startProfile( tr( "Layer style dock" ) );
1239   mMapStylingDock = new QgsDockWidget( this );
1240   mMapStylingDock->setWindowTitle( tr( "Layer Styling" ) );
1241   mMapStylingDock->setObjectName( QStringLiteral( "LayerStyling" ) );
1242   QShortcut *showStylingDock = new QShortcut( QKeySequence( tr( "Ctrl+3" ) ), this );
1243   connect( showStylingDock, &QShortcut::activated, mMapStylingDock, &QgsDockWidget::toggleUserVisible );
1244   showStylingDock->setObjectName( QStringLiteral( "ShowLayerStylingPanel" ) );
1245   showStylingDock->setWhatsThis( tr( "Show Style Panel" ) );
1246 
1247   mMapStyleWidget = new QgsLayerStylingWidget( mMapCanvas, mInfoBar, mMapLayerPanelFactories );
1248   mMapStylingDock->setWidget( mMapStyleWidget );
1249   connect( mMapStyleWidget, &QgsLayerStylingWidget::styleChanged, this, &QgisApp::updateLabelToolButtons );
1250   connect( mMapStylingDock, &QDockWidget::visibilityChanged, mActionStyleDock, &QAction::setChecked );
1251 
1252   addDockWidget( Qt::RightDockWidgetArea, mMapStylingDock );
1253   mMapStylingDock->hide();
1254   endProfile();
1255 
1256   startProfile( tr( "Developer tools dock" ) );
1257   mDevToolsDock = new QgsDockWidget( this );
1258   mDevToolsDock->setWindowTitle( tr( "Debugging/Development Tools" ) );
1259   mDevToolsDock->setObjectName( QStringLiteral( "DevTools" ) );
1260   QShortcut *showDevToolsDock = new QShortcut( QKeySequence( tr( "F12" ) ), this );
1261   connect( showDevToolsDock, &QShortcut::activated, mDevToolsDock, &QgsDockWidget::toggleUserVisible );
1262   showDevToolsDock->setObjectName( QStringLiteral( "ShowDevToolsPanel" ) );
1263   showDevToolsDock->setWhatsThis( tr( "Show Debugging/Development Tools" ) );
1264 
1265   mDevToolsWidget = new QgsDevToolsPanelWidget( mDevToolFactories );
1266   mDevToolsDock->setWidget( mDevToolsWidget );
1267 //  connect( mDevToolsDock, &QDockWidget::visibilityChanged, mActionStyleDock, &QAction::setChecked );
1268 
1269   addDockWidget( Qt::RightDockWidgetArea, mDevToolsDock );
1270   mDevToolsDock->hide();
1271   endProfile();
1272 
1273   startProfile( tr( "Snapping dialog" ) );
1274   mSnappingDialog = new QgsSnappingWidget( QgsProject::instance(), mMapCanvas, this );
1275   connect( mSnappingDialog, &QgsSnappingWidget::snappingConfigChanged, QgsProject::instance(), [ = ] { QgsProject::instance()->setSnappingConfig( mSnappingDialog->config() ); } );
1276   QString mainSnappingWidgetMode = QgsSettings().value( QStringLiteral( "/qgis/mainSnappingWidgetMode" ), "dialog" ).toString();
1277   if ( mainSnappingWidgetMode == QLatin1String( "dock" ) )
1278   {
1279     QgsDockWidget *dock = new QgsDockWidget( tr( "Snapping and Digitizing Options" ), QgisApp::instance() );
1280     dock->setAllowedAreas( Qt::AllDockWidgetAreas );
1281     dock->setWidget( mSnappingDialog );
1282     dock->setObjectName( QStringLiteral( "Snapping and Digitizing Options" ) );
1283     addDockWidget( Qt::LeftDockWidgetArea, dock );
1284     mSnappingDialogContainer = dock;
1285     dock->hide();
1286   }
1287   else
1288   {
1289     QDialog *dialog = new QDialog( this, Qt::Tool );
1290     dialog->setObjectName( QStringLiteral( "snappingSettings" ) );
1291     dialog->setWindowTitle( tr( "Project Snapping Settings" ) );
1292     QgsGui::instance()->enableAutoGeometryRestore( dialog );
1293     QVBoxLayout *layout = new QVBoxLayout( dialog );
1294     layout->addWidget( mSnappingDialog );
1295     layout->setContentsMargins( 0, 0, 0, 0 );
1296     mSnappingDialogContainer = dialog;
1297   }
1298   endProfile();
1299 
1300   mBrowserModel = new QgsBrowserGuiModel( this );
1301   mBrowserWidget = new QgsBrowserDockWidget( tr( "Browser" ), mBrowserModel, this );
1302   mBrowserWidget->setObjectName( QStringLiteral( "Browser" ) );
1303   mBrowserWidget->setMessageBar( mInfoBar );
1304 
1305   mTemporalControllerWidget = new QgsTemporalControllerDockWidget( tr( "Temporal Controller" ), this );
1306   mTemporalControllerWidget->setObjectName( QStringLiteral( "Temporal Controller" ) );
1307   addDockWidget( Qt::TopDockWidgetArea, mTemporalControllerWidget );
1308   mTemporalControllerWidget->hide();
1309   mTemporalControllerWidget->setToggleVisibilityAction( mActionTemporalController );
1310 
1311   mMapCanvas->setTemporalController( mTemporalControllerWidget->temporalController() );
1312   mTemporalControllerWidget->setMapCanvas( mMapCanvas );
1313 
1314   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsAppDirectoryItemGuiProvider() );
1315   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsAppFileItemGuiProvider() );
1316   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsProjectHomeItemGuiProvider() );
1317   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsProjectItemGuiProvider() );
1318   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsFavoritesItemGuiProvider() );
1319   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsLayerItemGuiProvider() );
1320   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsBookmarksItemGuiProvider() );
1321   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsFieldsItemGuiProvider() );
1322   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsFieldItemGuiProvider() );
1323   QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsDatabaseItemGuiProvider() );
1324 
1325   QShortcut *showBrowserDock = new QShortcut( QKeySequence( tr( "Ctrl+2" ) ), this );
1326   connect( showBrowserDock, &QShortcut::activated, mBrowserWidget, &QgsDockWidget::toggleUserVisible );
1327   showBrowserDock->setObjectName( QStringLiteral( "ShowBrowserPanel" ) );
1328   showBrowserDock->setWhatsThis( tr( "Show Browser Panel" ) );
1329 
1330   addDockWidget( Qt::LeftDockWidgetArea, mBrowserWidget );
1331   mBrowserWidget->hide();
1332   // Only connect the first widget: the model is shared, there is no need to refresh multiple times.
1333   connect( this, &QgisApp::connectionsChanged, mBrowserWidget, [ = ]
1334   {
1335     if ( !mBlockBrowser1Refresh && !mBlockBrowser2Refresh )
1336       mBrowserWidget->refresh();
1337   } );
1338   connect( mBrowserWidget, &QgsBrowserDockWidget::connectionsChanged, this, [ = ]
1339   {
1340     mBlockBrowser1Refresh++;
1341     emit connectionsChanged();
1342     mBlockBrowser1Refresh--;
1343   } );
1344   connect( mBrowserWidget, &QgsBrowserDockWidget::openFile, this, &QgisApp::openFile );
1345   connect( mBrowserWidget, &QgsBrowserDockWidget::handleDropUriList, this, &QgisApp::handleDropUriList );
1346 
1347   mBrowserWidget2 = new QgsBrowserDockWidget( tr( "Browser (2)" ), mBrowserModel, this );
1348   mBrowserWidget2->setObjectName( QStringLiteral( "Browser2" ) );
1349   addDockWidget( Qt::LeftDockWidgetArea, mBrowserWidget2 );
1350   mBrowserWidget2->hide();
1351   connect( mBrowserWidget2, &QgsBrowserDockWidget::connectionsChanged, this, [ = ]
1352   {
1353     mBlockBrowser2Refresh++;
1354     emit connectionsChanged();
1355     mBlockBrowser2Refresh--;
1356   } );
1357   connect( mBrowserWidget2, &QgsBrowserDockWidget::openFile, this, &QgisApp::openFile );
1358   connect( mBrowserWidget2, &QgsBrowserDockWidget::handleDropUriList, this, &QgisApp::handleDropUriList );
1359 
1360   addDockWidget( Qt::LeftDockWidgetArea, mAdvancedDigitizingDockWidget );
1361   mAdvancedDigitizingDockWidget->hide();
1362 
1363   addDockWidget( Qt::LeftDockWidgetArea, mStatisticalSummaryDockWidget );
1364   mStatisticalSummaryDockWidget->hide();
1365 
1366   addDockWidget( Qt::LeftDockWidgetArea, mBookMarksDockWidget );
1367   mBookMarksDockWidget->hide();
1368 
1369   // create the GPS tool on starting QGIS - this is like the browser
1370   mpGpsWidget = new QgsGpsInformationWidget( mMapCanvas );
1371   QgsPanelWidgetStack *gpsStack = new QgsPanelWidgetStack();
1372   gpsStack->setMainPanel( mpGpsWidget );
1373   mpGpsWidget->setDockMode( true );
1374   //create the dock widget
1375   mpGpsDock = new QgsDockWidget( tr( "GPS Information" ), this );
1376 
1377   QShortcut *showGpsDock = new QShortcut( QKeySequence( tr( "Ctrl+0" ) ), this );
1378   connect( showGpsDock, &QShortcut::activated, mpGpsDock, &QgsDockWidget::toggleUserVisible );
1379   showGpsDock->setObjectName( QStringLiteral( "ShowGpsPanel" ) );
1380   showGpsDock->setWhatsThis( tr( "Show GPS Information Panel" ) );
1381 
1382   mpGpsDock->setObjectName( QStringLiteral( "GPSInformation" ) );
1383   mpGpsDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
1384   addDockWidget( Qt::LeftDockWidgetArea, mpGpsDock );
1385   // add to the Panel submenu
1386   // now add our widget to the dock - ownership of the widget is passed to the dock
1387   mpGpsDock->setWidget( gpsStack );
1388   mpGpsDock->hide();
1389 
1390   mLastMapToolMessage = nullptr;
1391 
1392   mLogViewer = new QgsMessageLogViewer( this );
1393 
1394   mLogDock = new QgsDockWidget( tr( "Log Messages" ), this );
1395   mLogDock->setObjectName( QStringLiteral( "MessageLog" ) );
1396   mLogDock->setAllowedAreas( Qt::AllDockWidgetAreas );
1397   addDockWidget( Qt::BottomDockWidgetArea, mLogDock );
1398   mLogDock->setWidget( mLogViewer );
1399   mLogDock->hide();
1400   connect( mMessageButton, &QAbstractButton::toggled, mLogDock, &QgsDockWidget::setUserVisible );
1401   connect( mLogDock, &QgsDockWidget::visibilityChanged, mMessageButton, &QAbstractButton::setChecked );
1402   connect( QgsApplication::messageLog(), static_cast < void ( QgsMessageLog::* )( bool ) >( &QgsMessageLog::messageReceived ), this, &QgisApp::toggleLogMessageIcon );
1403   connect( mMessageButton, &QAbstractButton::toggled, this, &QgisApp::toggleLogMessageIcon );
1404   mVectorLayerTools = new QgsGuiVectorLayerTools();
1405 
1406   // Init the editor widget types
1407   QgsGui::editorWidgetRegistry()->initEditors( mMapCanvas, mInfoBar );
1408 
1409   mInternalClipboard = new QgsClipboard; // create clipboard
1410   connect( mInternalClipboard, &QgsClipboard::changed, this, &QgisApp::clipboardChanged );
1411   mQgisInterface = new QgisAppInterface( this ); // create the interface
1412 
1413 #ifdef Q_OS_MAC
1414   // action for Window menu (create before generating WindowTitleChange event))
1415   mWindowAction = new QAction( this );
1416   connect( mWindowAction, SIGNAL( triggered() ), this, SLOT( activate() ) );
1417 
1418   // add this window to Window menu
1419   addWindow( mWindowAction );
1420 #endif
1421 
1422   registerMapLayerPropertiesFactory( new QgsVectorLayerDigitizingPropertiesFactory( this ) );
1423   registerMapLayerPropertiesFactory( new QgsPointCloudRendererWidgetFactory( this ) );
1424 #ifdef HAVE_3D
1425   registerMapLayerPropertiesFactory( new QgsVectorLayer3DRendererWidgetFactory( this ) );
1426   registerMapLayerPropertiesFactory( new QgsMeshLayer3DRendererWidgetFactory( this ) );
1427   registerMapLayerPropertiesFactory( new QgsPointCloudLayer3DRendererWidgetFactory( this ) );
1428 #endif
1429   registerMapLayerPropertiesFactory( new QgsPointCloudElevationPropertiesWidgetFactory( this ) );
1430   registerMapLayerPropertiesFactory( new QgsAnnotationItemPropertiesWidgetFactory( this ) );
1431 
1432   activateDeactivateLayerRelatedActions( nullptr ); // after members were created
1433 
1434   connect( QgsGui::mapLayerActionRegistry(), &QgsMapLayerActionRegistry::changed, this, &QgisApp::refreshActionFeatureAction );
1435 
1436   // set application's caption
1437   QString caption = tr( "QGIS - %1 ('%2')" ).arg( Qgis::version(), Qgis::releaseName() );
1438   setWindowTitle( caption );
1439 
1440   // QgsMessageLog::logMessage( tr( "QGIS starting…" ), QString(), Qgis::MessageLevel::Info );
1441 
1442   connect( QgsProject::instance(), &QgsProject::isDirtyChanged, this, [ = ] { setTitleBarText_( *this ); } );
1443 
1444   // set QGIS specific srs validation
1445   connect( this, &QgisApp::customCrsValidation,
1446            this, &QgisApp::validateCrs );
1447   QgsCoordinateReferenceSystem::setCustomCrsValidation( customSrsValidation_ );
1448 
1449   // set graphical message output
1450   QgsMessageOutput::setMessageOutputCreator( messageOutputViewer_ );
1451 
1452   // set graphical credential requester
1453   new QgsCredentialDialog( this );
1454 
1455   mLocatorWidget->setMapCanvas( mMapCanvas );
1456   connect( mLocatorWidget, &QgsLocatorWidget::configTriggered, this, [ = ] { showOptionsDialog( this, QStringLiteral( "mOptionsLocatorSettings" ) ); } );
1457 
1458   qApp->processEvents();
1459 
1460   // load providers
1461   mSplash->showMessage( tr( "Checking provider plugins" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
1462   qApp->processEvents();
1463 
1464   // Setup QgsNetworkAccessManager (this needs to happen after authentication, for proxy settings)
1465   namSetup();
1466 
1467 
1468 #ifdef HAVE_OPENCL
1469   // Setup the default OpenCL programs source path, this my be overridden later by main.cpp startup
1470   QgsOpenClUtils::setSourcePath( QDir( QgsApplication::pkgDataPath() ).absoluteFilePath( QStringLiteral( "resources/opencl_programs" ) ) );
1471 #endif
1472 
1473 
1474   QgsApplication::dataItemProviderRegistry()->addProvider( new QgsBookmarksDataItemProvider() );
1475   registerCustomDropHandler( new QgsBookmarkDropHandler() );
1476   QgsApplication::dataItemProviderRegistry()->addProvider( new QgsQlrDataItemProvider() );
1477   registerCustomDropHandler( new QgsQlrDropHandler() );
1478   QgsApplication::dataItemProviderRegistry()->addProvider( new QgsQptDataItemProvider() );
1479   registerCustomDropHandler( new QgsQptDropHandler() );
1480   QgsApplication::dataItemProviderRegistry()->addProvider( new QgsStyleXmlDataItemProvider() );
1481   registerCustomDropHandler( new QgsStyleXmlDropHandler() );
1482   QgsApplication::dataItemProviderRegistry()->addProvider( new QgsHtmlDataItemProvider() );
1483 
1484   // set handler for missing layers (will be owned by QgsProject)
1485   mAppBadLayersHandler = new QgsHandleBadLayersHandler();
1486   QgsProject::instance()->setBadLayerHandler( mAppBadLayersHandler );
1487 
1488   mSplash->showMessage( tr( "Starting Python" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
1489   qApp->processEvents();
1490   loadPythonSupport();
1491 
1492 #ifdef WITH_BINDINGS
1493   QgsApplication::dataItemProviderRegistry()->addProvider( new QgsPyDataItemProvider() );
1494   registerCustomDropHandler( new QgsPyDropHandler() );
1495 #endif
1496 
1497   QgsApplication::dataItemProviderRegistry()->addProvider( new QgsProjectDataItemProvider() );
1498 
1499   // now when all data item providers are registered, customize both browsers
1500   QgsCustomization::instance()->updateBrowserWidget( mBrowserWidget );
1501   QgsCustomization::instance()->updateBrowserWidget( mBrowserWidget2 );
1502 
1503 
1504   // populate annotation toolbar with initial items...
1505   const QList< int > itemMetadataIds = QgsGui::annotationItemGuiRegistry()->itemMetadataIds();
1506   for ( int id : itemMetadataIds )
1507   {
1508     annotationItemTypeAdded( id );
1509   }
1510   //..and listen out for new item types
1511   connect( QgsGui::annotationItemGuiRegistry(), &QgsAnnotationItemGuiRegistry::typeAdded, this, &QgisApp::annotationItemTypeAdded );
1512 
1513 
1514   // Create the plugin registry and load plugins
1515   // load any plugins that were running in the last session
1516   mSplash->showMessage( tr( "Restoring loaded plugins" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
1517   qApp->processEvents();
1518   QgsPluginRegistry::instance()->setQgisInterface( mQgisInterface );
1519 
1520   if ( restorePlugins )
1521   {
1522     // Restoring of plugins can be disabled with --noplugins command line option
1523     // because some plugins may cause QGIS to crash during startup
1524     QgsPluginRegistry::instance()->restoreSessionPlugins( QgsApplication::pluginPath() );
1525 
1526     // Also restore plugins from user specified plugin directories
1527     QString myPaths = settings.value( QStringLiteral( "plugins/searchPathsForPlugins" ), "" ).toString();
1528     if ( !myPaths.isEmpty() )
1529     {
1530       QStringList myPathList = myPaths.split( '|' );
1531       QgsPluginRegistry::instance()->restoreSessionPlugins( myPathList );
1532     }
1533   }
1534 
1535 #ifdef WITH_BINDINGS
1536   if ( mPythonUtils && mPythonUtils->isEnabled() )
1537   {
1538     startProfile( tr( "Plugin installer" ) );
1539     // initialize the plugin installer to start fetching repositories in background
1540     QgsPythonRunner::run( QStringLiteral( "import pyplugin_installer" ) );
1541     QgsPythonRunner::run( QStringLiteral( "pyplugin_installer.initPluginInstaller()" ) );
1542     // enable Python in the Plugin Manager and pass the PythonUtils to it
1543     mPluginManager->setPythonUtils( mPythonUtils );
1544     // add Python Console options
1545     initPythonConsoleOptions();
1546     endProfile();
1547   }
1548   else if ( mActionShowPythonDialog )
1549 #endif
1550   {
1551     // python is disabled so get rid of the action for python console
1552     // and installing plugin from ZUIP
1553     delete mActionShowPythonDialog;
1554     mActionShowPythonDialog = nullptr;
1555   }
1556 
1557   // Update recent project list (as possible custom project storages are now registered by plugins)
1558   mSplash->showMessage( tr( "Updating recent project paths" ), Qt::AlignHCenter | Qt::AlignBottom );
1559   qApp->processEvents();
1560   startProfile( tr( "Update recent project paths" ) );
1561   updateRecentProjectPaths();
1562   mWelcomePage->setRecentProjects( mRecentProjects );
1563   endProfile();
1564 
1565   // Set icon size of toolbars
1566   if ( settings.contains( QStringLiteral( "/qgis/iconSize" ) ) )
1567   {
1568     int size = settings.value( QStringLiteral( "/qgis/iconSize" ) ).toInt();
1569     if ( size < 16 )
1570       size = QGIS_ICON_SIZE;
1571     setIconSizes( size );
1572   }
1573   else
1574   {
1575     // first run, guess a good icon size
1576     int size = chooseReasonableDefaultIconSize();
1577     settings.setValue( QStringLiteral( "/qgis/iconSize" ), size );
1578     setIconSizes( size );
1579   }
1580 
1581   QgsApplication::validityCheckRegistry()->addCheck( new QgsLayoutScaleBarValidityCheck() );
1582   QgsApplication::validityCheckRegistry()->addCheck( new QgsLayoutNorthArrowValidityCheck() );
1583   QgsApplication::validityCheckRegistry()->addCheck( new QgsLayoutOverviewValidityCheck() );
1584   QgsApplication::validityCheckRegistry()->addCheck( new QgsLayoutPictureSourceValidityCheck() );
1585 
1586   mSplash->showMessage( tr( "Initializing file filters" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
1587   qApp->processEvents();
1588 
1589   // now build vector and raster file filters
1590   mVectorFileFilter = QgsProviderRegistry::instance()->fileVectorFilters();
1591   mRasterFileFilter = QgsProviderRegistry::instance()->fileRasterFilters();
1592 
1593 #if 0
1594   // Set the background color for toolbox and overview as they default to
1595   // white instead of the window color
1596   QPalette myPalette = toolBox->palette();
1597   myPalette.setColor( QPalette::Button, myPalette.window().color() );
1598   toolBox->setPalette( myPalette );
1599   //do the same for legend control
1600   myPalette = toolBox->palette();
1601   myPalette.setColor( QPalette::Button, myPalette.window().color() );
1602   mMapLegend->setPalette( myPalette );
1603   //and for overview control this is done in createOverView method
1604 #endif
1605   // Do this last in the ctor to ensure that all members are instantiated properly
1606   setupConnections();
1607   //
1608   // Please make sure this is the last thing the ctor does so that we can ensure the
1609   // widgets are all initialized before trying to restore their state.
1610   //
1611   mSplash->showMessage( tr( "Restoring window state" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
1612   qApp->processEvents();
1613   startProfile( tr( "Restore window state" ) );
1614   restoreWindowState();
1615   endProfile();
1616 
1617   // do main window customization - after window state has been restored, before the window is shown
1618   startProfile( tr( "Update customization on main window" ) );
1619   QgsCustomization::instance()->updateMainWindow( mToolbarMenu, mPanelMenu );
1620   endProfile();
1621 
1622   mSplash->showMessage( tr( "Populate saved styles" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
1623   startProfile( tr( "Populate saved styles" ) );
1624   QgsStyle::defaultStyle();
1625   endProfile();
1626 
1627   mSplash->showMessage( tr( "QGIS Ready!" ), Qt::AlignHCenter | Qt::AlignBottom, splashTextColor );
1628 
1629   QgsMessageLog::logMessage( QgsApplication::showSettings(), QString(), Qgis::MessageLevel::Info );
1630 
1631   //QgsMessageLog::logMessage( tr( "QGIS Ready!" ), QString(), Qgis::MessageLevel::Info );
1632 
1633   mMapTipsVisible = false;
1634   // This turns on the map tip if they where active in the last session
1635   if ( settings.value( QStringLiteral( "qgis/enableMapTips" ), false ).toBool() )
1636   {
1637     toggleMapTips( true );
1638   }
1639 
1640   mPythonMacrosEnabled = false;
1641 
1642   // setup drag drop
1643   setAcceptDrops( true );
1644 
1645   mFullScreenMode = false;
1646   mPrevScreenModeMaximized = false;
1647   startProfile( tr( "Show main window" ) );
1648   show();
1649   qApp->processEvents();
1650   endProfile();
1651 
1652   QgsGui::setWindowManager( new QgsAppWindowManager() );
1653 
1654   mMapCanvas->clearExtentHistory(); // reset zoomnext/zoomlast
1655 
1656   QShortcut *zoomInShortCut = new QShortcut( QKeySequence( tr( "Ctrl++" ) ), this );
1657   connect( zoomInShortCut, &QShortcut::activated, mMapCanvas, &QgsMapCanvas::zoomIn );
1658   zoomInShortCut->setObjectName( QStringLiteral( "ZoomInToCanvas" ) );
1659   zoomInShortCut->setWhatsThis( tr( "Zoom in to canvas" ) );
1660   zoomInShortCut->setProperty( "Icon", QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomIn.svg" ) ) );
1661 
1662   QShortcut *zoomShortCut2 = new QShortcut( QKeySequence( tr( "Ctrl+=" ) ), this );
1663   connect( zoomShortCut2, &QShortcut::activated, mMapCanvas, &QgsMapCanvas::zoomIn );
1664   zoomShortCut2->setObjectName( QStringLiteral( "ZoomInToCanvas2" ) );
1665   zoomShortCut2->setWhatsThis( tr( "Zoom in to canvas (secondary)" ) );
1666   zoomShortCut2->setProperty( "Icon", QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomIn.svg" ) ) );
1667 
1668   QShortcut *zoomOutShortCut = new QShortcut( QKeySequence( tr( "Ctrl+-" ) ), this );
1669   connect( zoomOutShortCut, &QShortcut::activated, mMapCanvas, &QgsMapCanvas::zoomOut );
1670   zoomOutShortCut->setObjectName( QStringLiteral( "ZoomOutOfCanvas" ) );
1671   zoomOutShortCut->setWhatsThis( tr( "Zoom out of canvas" ) );
1672   zoomOutShortCut->setProperty( "Icon", QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomOut.svg" ) ) );
1673 
1674   //also make ctrl+alt+= a shortcut to switch to zoom in map tool
1675   QShortcut *zoomInToolShortCut = new QShortcut( QKeySequence( tr( "Ctrl+Alt+=" ) ), this );
1676   connect( zoomInToolShortCut, &QShortcut::activated, this, &QgisApp::zoomIn );
1677   zoomInToolShortCut->setObjectName( QStringLiteral( "ZoomIn2" ) );
1678   zoomInToolShortCut->setWhatsThis( tr( "Zoom in (secondary)" ) );
1679   zoomInToolShortCut->setProperty( "Icon", QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomIn.svg" ) ) );
1680 
1681   QShortcut *shortcutTracing = new QShortcut( QKeySequence( tr( "Ctrl+Shift+." ) ), this );
1682   connect( shortcutTracing, &QShortcut::activated, this, &QgisApp::toggleEventTracing );
1683 
1684   if ( ! QTouchDevice::devices().isEmpty() )
1685   {
1686     //add reacting to long click in touch
1687     grabGesture( Qt::TapAndHoldGesture );
1688   }
1689 
1690   connect( QgsApplication::taskManager(), &QgsTaskManager::statusChanged, this, &QgisApp::onTaskCompleteShowNotify );
1691 
1692   QgsGui::instance()->nativePlatformInterface()->initializeMainWindow( windowHandle(),
1693       QgsApplication::applicationName(),
1694       QgsApplication::organizationName(),
1695       Qgis::version() );
1696   connect( QgsGui::instance()->nativePlatformInterface(), &QgsNative::usbStorageNotification, mBrowserModel, &QgsBrowserModel::refreshDrives );
1697 
1698   // setup application progress reports from task manager
1699   connect( QgsApplication::taskManager(), &QgsTaskManager::taskAdded, this, []
1700   {
1701     QgsGui::instance()->nativePlatformInterface()->showUndefinedApplicationProgress();
1702   } );
1703   connect( QgsApplication::taskManager(), &QgsTaskManager::finalTaskProgressChanged, this, []( double val )
1704   {
1705     QgsGui::instance()->nativePlatformInterface()->setApplicationProgress( val );
1706   } );
1707   connect( QgsApplication::taskManager(), &QgsTaskManager::allTasksFinished, this, []
1708   {
1709     QgsGui::instance()->nativePlatformInterface()->hideApplicationProgress();
1710   } );
1711   connect( QgsApplication::taskManager(), &QgsTaskManager::countActiveTasksChanged, this, []( int count )
1712   {
1713     QgsGui::instance()->nativePlatformInterface()->setApplicationBadgeCount( count );
1714   } );
1715 
1716   ( void )new QgsAppMissingGridHandler( this );
1717 
1718   // supposedly all actions have been added, now register them to the shortcut manager
1719   QgsGui::shortcutsManager()->registerAllChildren( this );
1720   QgsGui::shortcutsManager()->registerAllChildren( mSnappingWidget );
1721 
1722   // register additional action
1723   auto registerShortcuts = [ = ]( const QString & sequence, const QString & objectName, const QString & whatsThis )
1724   {
1725     QShortcut *sc = new QShortcut( QKeySequence( sequence ), this );
1726     sc->setContext( Qt::ApplicationShortcut );
1727     sc->setObjectName( objectName );
1728     sc->setWhatsThis( whatsThis );
1729     QgsGui::shortcutsManager()->registerShortcut( sc, sequence );
1730   };
1731   registerShortcuts( QStringLiteral( "Ctrl+Alt+{" ), QStringLiteral( "mAttributeTableFirstEditedFeature" ), tr( "Edit first feature in attribute table" ) );
1732   registerShortcuts( QStringLiteral( "Ctrl+Alt+[" ), QStringLiteral( "mAttributeTablePreviousEditedFeature" ), tr( "Edit previous feature in attribute table" ) );
1733   registerShortcuts( QStringLiteral( "Ctrl+Alt+]" ), QStringLiteral( "mAttributeTableNextEditedFeature" ), tr( "Edit next feature in attribute table" ) );
1734   registerShortcuts( QStringLiteral( "Ctrl+Alt+}" ), QStringLiteral( "mAttributeTableLastEditedFeature" ), tr( "Edit last feature in attribute table" ) );
1735 
1736   QgsGui::providerGuiRegistry()->registerGuis( this );
1737 
1738   setupLayoutManagerConnections();
1739 
1740   setupDuplicateFeaturesAction();
1741 
1742   // support for project storage
1743   connect( mProjectFromStorageMenu, &QMenu::aboutToShow, this, [this] { populateProjectStorageMenu( mProjectFromStorageMenu, false ); } );
1744   connect( mProjectToStorageMenu, &QMenu::aboutToShow, this, [this] { populateProjectStorageMenu( mProjectToStorageMenu, true ); } );
1745 
1746   QList<QAction *> actions = mPanelMenu->actions();
1747   std::sort( actions.begin(), actions.end(), cmpByText_ );
1748   mPanelMenu->insertActions( nullptr, actions );
1749 
1750   mBearingNumericFormat.reset( QgsLocalDefaultSettings::bearingFormat() );
1751 
1752   mNetworkLoggerWidgetFactory.reset( std::make_unique< QgsNetworkLoggerWidgetFactory >( mNetworkLogger ) );
1753 
1754   // update windows
1755   qApp->processEvents();
1756 
1757   // notify user if authentication system is disabled
1758   ( void )QgsAuthGuiUtils::isDisabled( messageBar() );
1759 
1760   startProfile( tr( "New project" ) );
1761   fileNewBlank(); // prepare empty project, also skips any default templates from loading
1762   updateCrsStatusBar();
1763   endProfile();
1764 
1765   connect( qobject_cast< QgsMapToolModifyAnnotation * >( mMapTools->mapTool( QgsAppMapTools::AnnotationEdit ) ), &QgsMapToolModifyAnnotation::itemSelected,
1766            mMapStyleWidget, &QgsLayerStylingWidget::setAnnotationItem );
1767   connect( qobject_cast< QgsMapToolModifyAnnotation * >( mMapTools->mapTool( QgsAppMapTools::AnnotationEdit ) ), &QgsMapToolModifyAnnotation::selectionCleared,
1768            mMapStyleWidget, [this] { mMapStyleWidget->setAnnotationItem( nullptr, QString() ); } );
1769 
1770   // request notification of FileOpen events (double clicking a file icon in Mac OS X Finder)
1771   // should come after fileNewBlank to ensure project is properly set up to receive any data source files
1772   QgsApplication::setFileOpenEventReceiver( this );
1773 
1774 #ifdef ANDROID
1775   toggleFullScreen();
1776 #endif
1777 
1778   mStartupProfilerWidgetFactory.reset( std::make_unique< QgsProfilerWidgetFactory >( profiler ) );
1779 
1780   auto toggleRevert = [ = ]
1781   {
1782     mActionRevertProject->setEnabled( QgsProject::instance()->isDirty() &&!QgsProject::instance()->fileName().isEmpty() );
1783   };
1784   connect( QgsProject::instance(), &QgsProject::isDirtyChanged, mActionRevertProject, toggleRevert );
1785   connect( QgsProject::instance(), &QgsProject::fileNameChanged, mActionRevertProject, toggleRevert );
1786 
1787   connect( QgsProject::instance()->displaySettings(), &QgsProjectDisplaySettings::bearingFormatChanged, this, [ = ]
1788   {
1789     mBearingNumericFormat.reset( QgsProject::instance()->displaySettings()->bearingFormat()->clone() );
1790   } );
1791   connect( mMapCanvas, &QgsMapCanvas::panDistanceBearingChanged, this, &QgisApp::showPanMessage );
1792 
1793   // the most important part of the initialization: make sure that people can play puzzle if they need
1794   QgsPuzzleWidget *puzzleWidget = new QgsPuzzleWidget( mMapCanvas );
1795   mCentralContainer->insertWidget( 2, puzzleWidget );
1796   connect( mCoordsEdit, &QgsStatusBarCoordinatesWidget::weAreBored, this, [ this, puzzleWidget ]
1797   {
1798     if ( puzzleWidget->letsGetThePartyStarted() )
1799       mCentralContainer->setCurrentIndex( 2 );
1800   } );
1801   connect( puzzleWidget, &QgsPuzzleWidget::done, this, [ this ]
1802   {
1803     mCentralContainer->setCurrentIndex( 0 );
1804   } );
1805 
1806   mCodeEditorWidgetFactory.reset( std::make_unique< QgsCodeEditorOptionsFactory >() );
1807   mBabelGpsDevicesWidgetFactory.reset( std::make_unique< QgsGpsDeviceOptionsFactory >() );
1808 
1809 #ifdef HAVE_3D
1810   m3DOptionsWidgetFactory.reset( std::make_unique< Qgs3DOptionsFactory >() );
1811 #endif
1812 }
1813 
QgisApp()1814 QgisApp::QgisApp()
1815   : QMainWindow( nullptr, Qt::WindowFlags() )
1816 #ifdef Q_OS_MAC
1817   , mWindowMenu( nullptr )
1818 #endif
1819 {
1820   sInstance = this;
1821   setupUi( this );
1822   mInternalClipboard = new QgsClipboard;
1823   mMapCanvas = new QgsMapCanvas();
1824   connect( mMapCanvas, &QgsMapCanvas::messageEmitted, this, &QgisApp::displayMessage );
1825   QgsCanvasRefreshBlocker refreshBlocker;
1826   mLayerTreeView = new QgsLayerTreeView( this );
1827   QgsLayerTreeModel *model = new QgsLayerTreeModel( QgsProject::instance()->layerTreeRoot(), this );
1828   mLayerTreeView->setModel( model );
1829   mUndoWidget = new QgsUndoWidget( nullptr, mMapCanvas );
1830   mUserInputDockWidget = new QgsUserInputWidget( this );
1831   mInfoBar = new QgsMessageBar( centralWidget() );
1832   mLayerTreeView->setMessageBar( mInfoBar );
1833   mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this );
1834   mPanelMenu = new QMenu( this );
1835   mProgressBar = new QProgressBar( this );
1836   mStatusBar = new QgsStatusBar( this );
1837 
1838   mBearingNumericFormat.reset( QgsLocalDefaultSettings::bearingFormat() );
1839   // More tests may need more members to be initialized
1840 }
1841 
~QgisApp()1842 QgisApp::~QgisApp()
1843 {
1844   // shouldn't be needed, but from this stage on, we don't want/need ANY map canvas refreshes to take place
1845   mFreezeCount = 1000000;
1846 
1847 #ifdef HAVE_GEOREFERENCER
1848   if ( mGeoreferencer )
1849   {
1850     delete mGeoreferencer;
1851     mGeoreferencer = nullptr;
1852   }
1853 #endif
1854 
1855   mNetworkLoggerWidgetFactory.reset();
1856 
1857   delete mInternalClipboard;
1858   delete mQgisInterface;
1859   delete mStyleSheetBuilder;
1860 
1861   if ( QgsMapTool *tool = mMapCanvas->mapTool() )
1862     mMapCanvas->unsetMapTool( tool );
1863   mMapTools.reset();
1864 
1865   // must come after deleting map tools
1866   delete mAdvancedDigitizingDockWidget;
1867   mAdvancedDigitizingDockWidget = nullptr;
1868 
1869   delete mpMaptip;
1870 
1871   delete mpGpsWidget;
1872 
1873   delete mOverviewMapCursor;
1874 
1875   delete mTracer;
1876 
1877   delete mVectorLayerTools;
1878   delete mWelcomePage;
1879   delete mBookMarksDockWidget;
1880 
1881   // Gracefully delete window manager now
1882   QgsGui::setWindowManager( nullptr );
1883 
1884   deleteLayoutDesigners();
1885   removeAnnotationItems();
1886 
1887   // cancel request for FileOpen events
1888   QgsApplication::setFileOpenEventReceiver( nullptr );
1889 
1890   unregisterCustomLayoutDropHandler( mLayoutQptDropHandler );
1891   unregisterCustomLayoutDropHandler( mLayoutImageDropHandler );
1892 
1893 #ifdef WITH_BINDINGS
1894   delete mPythonUtils;
1895 #endif
1896 
1897   delete mDataSourceManagerDialog;
1898   qDeleteAll( mCustomDropHandlers );
1899   qDeleteAll( mCustomLayoutDropHandlers );
1900 
1901   const QList<QgsMapCanvas *> canvases = mapCanvases();
1902   for ( QgsMapCanvas *canvas : canvases )
1903   {
1904     delete canvas;
1905   }
1906 
1907   // these may have references to map layers which need to be cleaned up
1908   mBrowserWidget->close(); // close first, to trigger save of state
1909   delete mBrowserWidget;
1910   mBrowserWidget = nullptr;
1911   delete mBrowserWidget2;
1912   mBrowserWidget2 = nullptr;
1913   delete mBrowserModel;
1914   mBrowserModel = nullptr;
1915   delete mGeometryValidationDock;
1916   mGeometryValidationDock = nullptr;
1917   delete mSnappingUtils;
1918   mSnappingUtils = nullptr;
1919   delete mUserInputDockWidget;
1920   mUserInputDockWidget = nullptr;
1921   delete mMapStylingDock;
1922   mMapStylingDock = nullptr;
1923 
1924   QgsGui::instance()->nativePlatformInterface()->cleanup();
1925 
1926   // This function *MUST* be the last one called, as it destroys in
1927   // particular GDAL. As above objects can hold GDAL/OGR objects, it is not
1928   // safe destroying them afterwards
1929   QgsApplication::exitQgis();
1930   // Do *NOT* add anything here !
1931 }
1932 
dragEnterEvent(QDragEnterEvent * event)1933 void QgisApp::dragEnterEvent( QDragEnterEvent *event )
1934 {
1935   if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( QStringLiteral( "application/x-vnd.qgis.qgis.uri" ) ) )
1936   {
1937     // the mime data are coming from layer tree, so ignore that, do not import those layers again
1938     if ( !event->mimeData()->hasFormat( QStringLiteral( "application/qgis.layertreemodeldata" ) ) )
1939       event->acceptProposedAction();
1940   }
1941 
1942   // check if any custom handlers can operate on the data
1943   const QVector<QPointer<QgsCustomDropHandler >> handlers = mCustomDropHandlers;
1944   for ( QgsCustomDropHandler *handler : handlers )
1945   {
1946     if ( handler && handler->canHandleMimeData( event->mimeData() ) )
1947     {
1948       event->acceptProposedAction();
1949       return;
1950     }
1951   }
1952 }
1953 
dropEvent(QDropEvent * event)1954 void QgisApp::dropEvent( QDropEvent *event )
1955 {
1956   // dragging app is locked for the duration of dropEvent. This causes explorer windows to hang
1957   // while large projects/layers are loaded. So instead we return from dropEvent as quickly as possible
1958   // and do the actual handling of the drop after a very short timeout
1959   QTimer *timer = new QTimer( this );
1960   timer->setSingleShot( true );
1961   timer->setInterval( 50 );
1962 
1963   // first, allow custom handlers to directly operate on the mime data
1964   const QVector<QPointer<QgsCustomDropHandler >> handlers = mCustomDropHandlers;
1965   for ( QgsCustomDropHandler *handler : handlers )
1966   {
1967     if ( handler )
1968     {
1969       if ( handler->handleMimeDataV2( event->mimeData() ) )
1970       {
1971         // custom handler completely handled this data, no further processing required
1972         event->acceptProposedAction();
1973         return;
1974       }
1975 
1976       Q_NOWARN_DEPRECATED_PUSH
1977       handler->handleMimeData( event->mimeData() );
1978       Q_NOWARN_DEPRECATED_POP
1979     }
1980   }
1981 
1982   // get the file list
1983   QList<QUrl>::iterator i;
1984   QList<QUrl>urls = event->mimeData()->urls();
1985   QStringList files;
1986   for ( i = urls.begin(); i != urls.end(); ++i )
1987   {
1988     QString fileName = i->toLocalFile();
1989     // seems that some drag and drop operations include an empty url
1990     // so we test for length to make sure we have something
1991     if ( !fileName.isEmpty() )
1992     {
1993       files << fileName;
1994     }
1995   }
1996 
1997   QgsMimeDataUtils::UriList lst;
1998   if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
1999   {
2000     lst = QgsMimeDataUtils::decodeUriList( event->mimeData() );
2001   }
2002 
2003   connect( timer, &QTimer::timeout, this, [this, timer, files, lst]
2004   {
2005     QgsCanvasRefreshBlocker refreshBlocker;
2006 
2007     for ( const QString &file : std::as_const( files ) )
2008     {
2009       bool handled = false;
2010 
2011       // give custom drop handlers first priority at handling the file
2012       const QVector<QPointer<QgsCustomDropHandler >> handlers = mCustomDropHandlers;
2013       for ( QgsCustomDropHandler *handler : handlers )
2014       {
2015         if ( handler && handler->handleFileDrop( file ) )
2016         {
2017           handled = true;
2018           break;
2019         }
2020       }
2021 
2022       if ( !handled )
2023         openFile( file );
2024     }
2025 
2026     if ( !lst.isEmpty() )
2027     {
2028       handleDropUriList( lst );
2029     }
2030 
2031     timer->deleteLater();
2032   } );
2033 
2034   event->acceptProposedAction();
2035   timer->start();
2036 }
2037 
annotationCreated(QgsAnnotation * annotation)2038 void QgisApp::annotationCreated( QgsAnnotation *annotation )
2039 {
2040   const auto canvases = mapCanvases();
2041   // create canvas annotation item for annotation
2042   for ( QgsMapCanvas *canvas : canvases )
2043   {
2044     QgsMapCanvasAnnotationItem *canvasItem = new QgsMapCanvasAnnotationItem( annotation, canvas );
2045     Q_UNUSED( canvasItem ) //item is already added automatically to canvas scene
2046   }
2047 }
2048 
registerCustomDropHandler(QgsCustomDropHandler * handler)2049 void QgisApp::registerCustomDropHandler( QgsCustomDropHandler *handler )
2050 {
2051   if ( !mCustomDropHandlers.contains( handler ) )
2052     mCustomDropHandlers << handler;
2053 
2054   const auto canvases = mapCanvases();
2055   for ( QgsMapCanvas *canvas : canvases )
2056   {
2057     canvas->setCustomDropHandlers( mCustomDropHandlers );
2058   }
2059 }
2060 
unregisterCustomDropHandler(QgsCustomDropHandler * handler)2061 void QgisApp::unregisterCustomDropHandler( QgsCustomDropHandler *handler )
2062 {
2063   mCustomDropHandlers.removeOne( handler );
2064 
2065   const auto canvases = mapCanvases();
2066   for ( QgsMapCanvas *canvas : canvases )
2067   {
2068     canvas->setCustomDropHandlers( mCustomDropHandlers );
2069   }
2070 }
2071 
registerCustomProjectOpenHandler(QgsCustomProjectOpenHandler * handler)2072 void QgisApp::registerCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
2073 {
2074   if ( !mCustomProjectOpenHandlers.contains( handler ) )
2075     mCustomProjectOpenHandlers << handler;
2076 }
2077 
unregisterCustomProjectOpenHandler(QgsCustomProjectOpenHandler * handler)2078 void QgisApp::unregisterCustomProjectOpenHandler( QgsCustomProjectOpenHandler *handler )
2079 {
2080   mCustomProjectOpenHandlers.removeOne( handler );
2081 }
2082 
customDropHandlers() const2083 QVector<QPointer<QgsCustomDropHandler> > QgisApp::customDropHandlers() const
2084 {
2085   return mCustomDropHandlers;
2086 }
2087 
registerCustomLayoutDropHandler(QgsLayoutCustomDropHandler * handler)2088 void QgisApp::registerCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler )
2089 {
2090   if ( !mCustomLayoutDropHandlers.contains( handler ) )
2091     mCustomLayoutDropHandlers << handler;
2092 }
2093 
unregisterCustomLayoutDropHandler(QgsLayoutCustomDropHandler * handler)2094 void QgisApp::unregisterCustomLayoutDropHandler( QgsLayoutCustomDropHandler *handler )
2095 {
2096   mCustomLayoutDropHandlers.removeOne( handler );
2097 }
2098 
customLayoutDropHandlers() const2099 QVector<QPointer<QgsLayoutCustomDropHandler> > QgisApp::customLayoutDropHandlers() const
2100 {
2101   return mCustomLayoutDropHandlers;
2102 }
2103 
handleDropUriList(const QgsMimeDataUtils::UriList & lst)2104 void QgisApp::handleDropUriList( const QgsMimeDataUtils::UriList &lst )
2105 {
2106   // avoid unnecessary work when adding lots of layers at once - defer emitting the active layer changed signal until we've
2107   // added all layers, and only emit the signal once for the final layer added
2108   mBlockActiveLayerChanged = true;
2109 
2110   QgsScopedProxyProgressTask task( tr( "Loading layers" ) );
2111 
2112 
2113   auto showLayerLoadWarnings = [ = ]( const QString & title, const QString & shortMessage, const QString & longMessage, Qgis::MessageLevel level )
2114   {
2115     QgsMessageBarItem *messageWidget = visibleMessageBar()->createMessage( title, shortMessage );
2116     QPushButton *detailsButton = new QPushButton( tr( "Details" ) );
2117     connect( detailsButton, &QPushButton::clicked, this, [ = ]
2118     {
2119       if ( QgsMessageViewer *dialog = dynamic_cast< QgsMessageViewer * >( QgsMessageOutput::createMessageOutput() ) )
2120       {
2121         dialog->setTitle( title );
2122         dialog->setMessage( longMessage, QgsMessageOutput::MessageHtml );
2123         dialog->showMessage();
2124       }
2125     } );
2126     messageWidget->layout()->addWidget( detailsButton );
2127     return visibleMessageBar()->pushWidget( messageWidget, level, 0 );
2128   };
2129 
2130   // insert items in reverse order as each one is inserted on top of previous one
2131   int count = 0;
2132   for ( int i = lst.size() - 1 ; i >= 0 ; i--, count++ )
2133   {
2134     const QgsMimeDataUtils::Uri &u = lst.at( i );
2135 
2136     QString uri = crsAndFormatAdjustedLayerUri( u.uri, u.supportedCrs, u.supportedFormats );
2137 
2138     if ( u.layerType == QLatin1String( "collection" ) )
2139     {
2140       openLayer( uri, true );
2141     }
2142     else if ( u.layerType == QLatin1String( "vector" ) )
2143     {
2144       addVectorLayer( uri, u.name, u.providerKey );
2145     }
2146     else if ( u.layerType == QLatin1String( "raster" ) )
2147     {
2148       addRasterLayer( uri, u.name, u.providerKey );
2149     }
2150     else if ( u.layerType == QLatin1String( "mesh" ) )
2151     {
2152       addMeshLayer( uri, u.name, u.providerKey );
2153     }
2154     else if ( u.layerType == QLatin1String( "pointcloud" ) )
2155     {
2156       addPointCloudLayer( uri, u.name, u.providerKey );
2157     }
2158     else if ( u.layerType == QLatin1String( "vector-tile" ) )
2159     {
2160       QgsTemporaryCursorOverride busyCursor( Qt::WaitCursor );
2161 
2162       const QgsVectorTileLayer::LayerOptions options( QgsProject::instance()->transformContext() );
2163       QgsVectorTileLayer *layer = new QgsVectorTileLayer( uri, u.name, options );
2164       bool ok = false;
2165       layer->loadDefaultMetadata( ok );
2166 
2167       QString error;
2168       QStringList warnings;
2169       bool res = layer->loadDefaultStyle( error, warnings );
2170       if ( res && !warnings.empty() )
2171       {
2172         QString message = QStringLiteral( "<p>%1</p>" ).arg( tr( "The following warnings were generated while converting the vector tile style:" ) );
2173         message += QLatin1String( "<ul>" );
2174 
2175         std::sort( warnings.begin(), warnings.end() );
2176         warnings.erase( std::unique( warnings.begin(), warnings.end() ), warnings.end() );
2177 
2178         for ( const QString &w : std::as_const( warnings ) )
2179         {
2180           message += QStringLiteral( "<li>%1</li>" ).arg( w.toHtmlEscaped().replace( '\n', QLatin1String( "<br>" ) ) );
2181         }
2182         message += QLatin1String( "</ul>" );
2183         showLayerLoadWarnings( tr( "Vector tiles" ), tr( "Style could not be completely converted" ),
2184                                message, Qgis::MessageLevel::Warning );
2185       }
2186       addMapLayer( layer );
2187     }
2188     else if ( u.layerType == QLatin1String( "plugin" ) )
2189     {
2190       addPluginLayer( uri, u.name, u.providerKey );
2191     }
2192     else if ( u.layerType == QLatin1String( "custom" ) )
2193     {
2194       const auto constMCustomDropHandlers = mCustomDropHandlers;
2195       for ( QgsCustomDropHandler *handler : constMCustomDropHandlers )
2196       {
2197         if ( handler && handler->customUriProviderKey() == u.providerKey )
2198         {
2199           handler->handleCustomUriDrop( u );
2200           break;
2201         }
2202       }
2203     }
2204     else if ( u.layerType == QLatin1String( "project" ) )
2205     {
2206       openFile( u.uri, QStringLiteral( "project" ) );
2207     }
2208 
2209     task.setProgress( 100.0 * static_cast< double >( count ) / lst.size() );
2210   }
2211 
2212   mBlockActiveLayerChanged = false;
2213   onActiveLayerChanged( activeLayer() );
2214 }
2215 
event(QEvent * event)2216 bool QgisApp::event( QEvent *event )
2217 {
2218   bool done = false;
2219   if ( event->type() == QEvent::FileOpen )
2220   {
2221     // handle FileOpen event (double clicking a file icon in Mac OS X Finder)
2222     QFileOpenEvent *foe = static_cast<QFileOpenEvent *>( event );
2223     openFile( foe->file() );
2224     done = true;
2225   }
2226   else if ( !QTouchDevice::devices().isEmpty() && event->type() == QEvent::Gesture )
2227   {
2228     done = gestureEvent( static_cast<QGestureEvent *>( event ) );
2229   }
2230   else
2231   {
2232     // pass other events to base class
2233     done = QMainWindow::event( event );
2234   }
2235   return done;
2236 }
2237 
2238 
visibleMessageBar()2239 QgsMessageBar *QgisApp::visibleMessageBar()
2240 {
2241   if ( mDataSourceManagerDialog &&
2242        mDataSourceManagerDialog->isVisible() &&
2243        mDataSourceManagerDialog->isModal() )
2244   {
2245     return mDataSourceManagerDialog->messageBar();
2246   }
2247   else
2248   {
2249     return messageBar();
2250   }
2251 }
2252 
findBrokenLayerDependencies(QgsVectorLayer * vl,QgsMapLayer::StyleCategories categories) const2253 const QList<QgsVectorLayerRef> QgisApp::findBrokenLayerDependencies( QgsVectorLayer *vl, QgsMapLayer::StyleCategories categories ) const
2254 {
2255   QList<QgsVectorLayerRef> brokenDependencies;
2256 
2257   if ( categories.testFlag( QgsMapLayer::StyleCategory::Forms ) )
2258   {
2259     for ( int i = 0; i < vl->fields().count(); i++ )
2260     {
2261       const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( vl, vl->fields().field( i ).name() );
2262       QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
2263       if ( fieldFormatter )
2264       {
2265         const QList<QgsVectorLayerRef> constDependencies { fieldFormatter->layerDependencies( setup.config() ) };
2266         for ( const QgsVectorLayerRef &dependency : constDependencies )
2267         {
2268           // I guess we need and isNull()/isValid() method for the ref
2269           if ( dependency.layer ||
2270                ! dependency.name.isEmpty() ||
2271                ! dependency.source.isEmpty() ||
2272                ! dependency.layerId.isEmpty() )
2273           {
2274             const QgsVectorLayer *depVl { QgsVectorLayerRef( dependency ).resolveWeakly( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ) };
2275             if ( ! depVl || ! depVl->isValid() )
2276             {
2277               brokenDependencies.append( dependency );
2278             }
2279           }
2280         }
2281       }
2282     }
2283   }
2284 
2285   if ( categories.testFlag( QgsMapLayer::StyleCategory::Relations ) )
2286   {
2287     // Check for layer weak relations
2288     const QList<QgsWeakRelation> constWeakRelations { vl->weakRelations() };
2289     for ( const QgsWeakRelation &rel : constWeakRelations )
2290     {
2291       QgsRelation relation { rel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ) };
2292       QgsVectorLayerRef dependency;
2293       bool found = false;
2294       if ( ! relation.isValid() )
2295       {
2296         // This is the big question: do we really
2297         // want to automatically load the referencing layer(s) too?
2298         // This could potentially lead to a cascaded load of a
2299         // long list of layers.
2300         // The code is in place but let's leave it disabled for now.
2301         if ( relation.referencedLayer() == vl )
2302         {
2303           // Do nothing because vl is the referenced layer
2304 #if 0
2305           dependency = rel.referencingLayer();
2306           found = true;
2307 #endif
2308         }
2309         else if ( relation.referencingLayer() == vl )
2310         {
2311           dependency = rel.referencedLayer();
2312           found = true;
2313         }
2314         else
2315         {
2316           // Something wrong is going on here, maybe this relation
2317           // does not really apply to this layer?
2318           QgsMessageLog::logMessage( tr( "None of the layers in the relation stored in the style match the current layer, skipping relation id: %1." ).arg( relation.id() ) );
2319         }
2320 
2321         if ( found )
2322         {
2323           // Make sure we don't add it twice if it was already added by the form widgets check
2324           bool refFound = false;
2325           for ( const QgsVectorLayerRef &otherRef : std::as_const( brokenDependencies ) )
2326           {
2327             if ( dependency.layerId == otherRef.layerId || ( dependency.source == otherRef.source && dependency.provider == otherRef.provider ) )
2328             {
2329               refFound = true;
2330               break;
2331             }
2332           }
2333           if ( ! refFound )
2334           {
2335             brokenDependencies.append( dependency );
2336           }
2337         }
2338       }
2339     }
2340   }
2341   return brokenDependencies;
2342 }
2343 
resolveVectorLayerDependencies(QgsVectorLayer * vl,QgsMapLayer::StyleCategories categories)2344 void QgisApp::resolveVectorLayerDependencies( QgsVectorLayer *vl, QgsMapLayer::StyleCategories categories )
2345 {
2346   if ( vl && vl->isValid() )
2347   {
2348     const auto constDependencies { findBrokenLayerDependencies( vl, categories ) };
2349     for ( const QgsVectorLayerRef &dependency : constDependencies )
2350     {
2351       // Check for projects without layer dependencies (see 7e8c7b3d0e094737336ff4834ea2af625d2921bf)
2352       if ( QgsProject::instance()->mapLayer( dependency.layerId ) || ( dependency.name.isEmpty() && dependency.source.isEmpty() ) )
2353       {
2354         continue;
2355       }
2356       // try to aggressively resolve the broken dependencies
2357       bool loaded = false;
2358       const QString providerName { vl->dataProvider()->name() };
2359       QgsProviderMetadata *providerMetadata { QgsProviderRegistry::instance()->providerMetadata( providerName ) };
2360       if ( providerMetadata )
2361       {
2362         // Retrieve the DB connection (if any)
2363 
2364         std::unique_ptr< QgsAbstractDatabaseProviderConnection > conn { QgsMapLayerUtils::databaseConnection( vl ) };
2365         if ( conn )
2366         {
2367           QString tableSchema;
2368           QString tableName;
2369           const QVariantMap sourceParts = providerMetadata->decodeUri( dependency.source );
2370 
2371           // This part should really be abstracted out to the connection classes or to the providers directly.
2372           // Different providers decode the uri differently, for example we don't get the table name out of OGR
2373           // but the layerName/layerId instead, so let's try different approaches
2374 
2375           // This works for GPKG
2376           tableName = sourceParts.value( QStringLiteral( "layerName" ) ).toString();
2377 
2378           // This works for PG and spatialite
2379           if ( tableName.isEmpty() )
2380           {
2381             tableName = sourceParts.value( QStringLiteral( "table" ) ).toString();
2382             tableSchema = sourceParts.value( QStringLiteral( "schema" ) ).toString();
2383           }
2384 
2385           // Helper to find layers in connections
2386           auto layerFinder = [ &conn, &dependency, &providerName ]( const QString & tableSchema, const QString & tableName ) -> bool
2387           {
2388             // First try the current schema (or no schema if it's not supported from the provider)
2389             try
2390             {
2391               const QString layerUri { conn->tableUri( tableSchema, tableName )};
2392               // Load it!
2393               std::unique_ptr< QgsVectorLayer > newVl = std::make_unique< QgsVectorLayer >( layerUri, dependency.name, providerName );
2394               if ( newVl->isValid() )
2395               {
2396                 QgsProject::instance()->addMapLayer( newVl.release() );
2397                 return true;
2398               }
2399             }
2400             catch ( QgsProviderConnectionException & )
2401             {
2402               // Do nothing!
2403             }
2404             return false;
2405           };
2406 
2407           loaded = layerFinder( tableSchema, tableName );
2408 
2409           // Try different schemas
2410           if ( ! loaded && conn->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::Schemas ) && ! tableSchema.isEmpty() )
2411           {
2412             const QStringList schemas { conn->schemas() };
2413             for ( const QString &schemaName : schemas )
2414             {
2415               if ( schemaName != tableSchema )
2416               {
2417                 loaded = layerFinder( schemaName, tableName );
2418               }
2419               if ( loaded )
2420               {
2421                 break;
2422               }
2423             }
2424           }
2425         }
2426       }
2427       if ( ! loaded )
2428       {
2429         const QString msg { tr( "layer '%1' requires layer '%2' to be loaded but '%2' could not be found, please load it manually if possible." ).arg( vl->name(), dependency.name ) };
2430         messageBar()->pushWarning( tr( "Missing layer form dependency" ), msg );
2431       }
2432       else
2433       {
2434         messageBar()->pushSuccess( tr( "Missing layer form dependency" ), tr( "Layer dependency '%2' required by '%1' was automatically loaded." )
2435                                    .arg( vl->name() )
2436                                    .arg( dependency.name ) );
2437       }
2438     }
2439   }
2440 }
2441 
resolveVectorLayerWeakRelations(QgsVectorLayer * vectorLayer)2442 void QgisApp::resolveVectorLayerWeakRelations( QgsVectorLayer *vectorLayer )
2443 {
2444   if ( vectorLayer && vectorLayer->isValid() )
2445   {
2446     const QList<QgsWeakRelation> constWeakRelations { vectorLayer->weakRelations( ) };
2447     for ( const QgsWeakRelation &rel : constWeakRelations )
2448     {
2449       QgsRelation relation { rel.resolvedRelation( QgsProject::instance(), QgsVectorLayerRef::MatchType::Name ) };
2450       if ( relation.isValid() )
2451       {
2452         // Avoid duplicates
2453         const QList<QgsRelation> constRelations { QgsProject::instance()->relationManager()->relations().values() };
2454         for ( const QgsRelation &other : constRelations )
2455         {
2456           if ( relation.hasEqualDefinition( other ) )
2457           {
2458             continue;
2459           }
2460         }
2461         QgsProject::instance()->relationManager()->addRelation( relation );
2462       }
2463     }
2464   }
2465 }
2466 
dataSourceManager(const QString & pageName)2467 void QgisApp::dataSourceManager( const QString &pageName )
2468 {
2469   if ( ! mDataSourceManagerDialog )
2470   {
2471     mDataSourceManagerDialog = new QgsDataSourceManagerDialog( mBrowserModel, this, mapCanvas() );
2472     connect( this, &QgisApp::connectionsChanged, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::refresh );
2473     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::connectionsChanged, this, &QgisApp::connectionsChanged );
2474     connect( mDataSourceManagerDialog, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ),
2475              this, SLOT( addRasterLayer( QString const &, QString const &, QString const & ) ) );
2476     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addRasterLayers, this, [ = ]( const QStringList & layersList ) { addRasterLayers( layersList ); } );
2477     connect( mDataSourceManagerDialog, SIGNAL( addVectorLayer( QString const &, QString const &, QString const & ) ),
2478              this, SLOT( addVectorLayer( QString const &, QString const &, QString const & ) ) );
2479     connect( mDataSourceManagerDialog, SIGNAL( addVectorLayers( QStringList const &, QString const &, QString const & ) ),
2480              this, SLOT( addVectorLayers( QStringList const &, QString const &, QString const & ) ) );
2481     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addMeshLayer, this, &QgisApp::addMeshLayer );
2482     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addVectorTileLayer, this, &QgisApp::addVectorTileLayer );
2483     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addPointCloudLayer, this, &QgisApp::addPointCloudLayer );
2484     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::showStatusMessage, this, &QgisApp::showStatusMessage );
2485     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addDatabaseLayers, this, &QgisApp::addDatabaseLayers );
2486     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::replaceSelectedVectorLayer, this, &QgisApp::replaceSelectedVectorLayer );
2487     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::handleDropUriList, this, &QgisApp::handleDropUriList );
2488     connect( this, &QgisApp::newProject, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::updateProjectHome );
2489     connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::openFile, this, &QgisApp::openFile );
2490 
2491   }
2492   else
2493   {
2494     mDataSourceManagerDialog->reset();
2495   }
2496   // Try to open the dialog on a particular page
2497   if ( ! pageName.isEmpty() )
2498   {
2499     mDataSourceManagerDialog->openPage( pageName );
2500   }
2501   if ( QgsSettings().value( QStringLiteral( "/qgis/dataSourceManagerNonModal" ), true ).toBool() )
2502   {
2503     mDataSourceManagerDialog->show();
2504   }
2505   else
2506   {
2507     mDataSourceManagerDialog->exec();
2508   }
2509 }
2510 
browserModel()2511 QgsBrowserGuiModel *QgisApp::browserModel()
2512 {
2513   return mBrowserModel;
2514 }
2515 
styleSheetBuilder()2516 QgisAppStyleSheet *QgisApp::styleSheetBuilder()
2517 {
2518   Q_ASSERT( mStyleSheetBuilder );
2519   return mStyleSheetBuilder;
2520 }
2521 
readRecentProjects()2522 void QgisApp::readRecentProjects()
2523 {
2524   QgsSettings settings;
2525   mRecentProjects.clear();
2526 
2527   settings.beginGroup( QStringLiteral( "UI" ) );
2528 
2529   // Migrate old recent projects if first time with new system
2530   if ( !settings.childGroups().contains( QStringLiteral( "recentProjects" ) ) )
2531   {
2532     QStringList oldRecentProjects = settings.value( QStringLiteral( "UI/recentProjectsList" ) ).toStringList();
2533 
2534     const auto constOldRecentProjects = oldRecentProjects;
2535     for ( const QString &project : constOldRecentProjects )
2536     {
2537       QgsRecentProjectItemsModel::RecentProjectData data;
2538       data.path = project;
2539       data.title = project;
2540 
2541       mRecentProjects.append( data );
2542     }
2543   }
2544   settings.endGroup();
2545 
2546   settings.beginGroup( QStringLiteral( "UI/recentProjects" ) );
2547   QStringList projectKeysList = settings.childGroups();
2548 
2549   //convert list to int values to obtain proper order
2550   QList<int> projectKeys;
2551   const auto constProjectKeysList = projectKeysList;
2552   for ( const QString &key : constProjectKeysList )
2553   {
2554     projectKeys.append( key.toInt() );
2555   }
2556   std::sort( projectKeys.begin(), projectKeys.end() );
2557 
2558   int pinPos = 0;
2559   const int maxProjects = QgsSettings().value( QStringLiteral( "maxRecentProjects" ), 20, QgsSettings::App ).toInt();
2560   for ( int i = 0; i < projectKeys.count(); ++i )
2561   {
2562     QgsRecentProjectItemsModel::RecentProjectData data;
2563     settings.beginGroup( QString::number( projectKeys.at( i ) ) );
2564     data.title = settings.value( QStringLiteral( "title" ) ).toString();
2565     data.path = settings.value( QStringLiteral( "path" ) ).toString();
2566     data.previewImagePath = settings.value( QStringLiteral( "previewImage" ) ).toString();
2567     data.crs = settings.value( QStringLiteral( "crs" ) ).toString();
2568     data.pin = settings.value( QStringLiteral( "pin" ) ).toBool();
2569     settings.endGroup();
2570     if ( data.pin )
2571     {
2572       mRecentProjects.insert( pinPos, data );
2573       pinPos++;
2574     }
2575     else
2576     {
2577       mRecentProjects.append( data );
2578     }
2579     if ( mRecentProjects.count() >= maxProjects )
2580       break;
2581   }
2582   settings.endGroup();
2583 }
2584 
applyProjectSettingsToCanvas(QgsMapCanvas * canvas)2585 void QgisApp::applyProjectSettingsToCanvas( QgsMapCanvas *canvas )
2586 {
2587   canvas->setCanvasColor( QgsProject::instance()->backgroundColor() );
2588   canvas->setSelectionColor( QgsProject::instance()->selectionColor() );
2589 }
2590 
applyDefaultSettingsToCanvas(QgsMapCanvas * canvas)2591 void QgisApp::applyDefaultSettingsToCanvas( QgsMapCanvas *canvas )
2592 {
2593   QgsSettings settings;
2594   canvas->enableAntiAliasing( settings.value( QStringLiteral( "qgis/enable_anti_aliasing" ), true ).toBool() );
2595   double zoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
2596   canvas->setWheelFactor( zoomFactor );
2597   canvas->setCachingEnabled( settings.value( QStringLiteral( "qgis/enable_render_caching" ), true ).toBool() );
2598   canvas->setParallelRenderingEnabled( settings.value( QStringLiteral( "qgis/parallel_rendering" ), true ).toBool() );
2599   canvas->setMapUpdateInterval( settings.value( QStringLiteral( "qgis/map_update_interval" ), 250 ).toInt() );
2600   canvas->setSegmentationTolerance( settings.value( QStringLiteral( "qgis/segmentationTolerance" ), "0.01745" ).toDouble() );
2601   canvas->setSegmentationToleranceType( QgsAbstractGeometry::SegmentationToleranceType( settings.enumValue( QStringLiteral( "qgis/segmentationToleranceType" ), QgsAbstractGeometry::MaximumAngle ) ) );
2602 }
2603 
chooseReasonableDefaultIconSize() const2604 int QgisApp::chooseReasonableDefaultIconSize() const
2605 {
2606   QScreen *screen = QApplication::screens().at( 0 );
2607   if ( screen->physicalDotsPerInch() < 115 )
2608   {
2609     // no hidpi screen, use default size
2610     return QGIS_ICON_SIZE;
2611   }
2612   else
2613   {
2614     double size = fontMetrics().horizontalAdvance( 'X' ) * 3;
2615     if ( size < 24 )
2616       return 16;
2617     else if ( size < 32 )
2618       return 24;
2619     else if ( size < 48 )
2620       return 32;
2621     else if ( size < 64 )
2622       return 48;
2623     else
2624       return 64;
2625   }
2626 
2627 }
2628 
readSettings()2629 void QgisApp::readSettings()
2630 {
2631   QgsSettings settings;
2632   QString themeName = settings.value( QStringLiteral( "UI/UITheme" ), "default" ).toString();
2633   setTheme( themeName );
2634 
2635   // Read legacy settings
2636   readRecentProjects();
2637 
2638   // this is a new session, reset enable macros value  when they are set for session
2639   Qgis::PythonMacroMode macroMode = settings.enumValue( QStringLiteral( "qgis/enableMacros" ), Qgis::PythonMacroMode::Ask );
2640   switch ( macroMode )
2641   {
2642     case Qgis::PythonMacroMode::NotForThisSession:
2643     case Qgis::PythonMacroMode::SessionOnly:
2644       settings.setEnumValue( QStringLiteral( "qgis/enableMacros" ), Qgis::PythonMacroMode::Ask );
2645       break;
2646 
2647     case Qgis::PythonMacroMode::Always:
2648     case Qgis::PythonMacroMode::Never:
2649     case Qgis::PythonMacroMode::Ask:
2650       break;
2651   }
2652 }
2653 
2654 
2655 //////////////////////////////////////////////////////////////////////
2656 //            Set Up the gui toolbars, menus, statusbar etc
2657 //////////////////////////////////////////////////////////////////////
2658 
createActions()2659 void QgisApp::createActions()
2660 {
2661   mActionPluginSeparator1 = nullptr;  // plugin list separator will be created when the first plugin is loaded
2662   mActionPluginSeparator2 = nullptr;  // python separator will be created only if python is found
2663   mActionRasterSeparator = nullptr;   // raster plugins list separator will be created when the first plugin is loaded
2664 
2665   // Project Menu Items
2666 
2667   connect( mActionNewProject, &QAction::triggered, this, [ = ] { fileNew(); } );
2668   connect( mActionNewBlankProject, &QAction::triggered, this, &QgisApp::fileNewBlank );
2669   connect( mActionOpenProject, &QAction::triggered, this, &QgisApp::fileOpen );
2670   connect( mActionRevertProject, &QAction::triggered, this, &QgisApp::fileRevert );
2671   connect( mActionSaveProject, &QAction::triggered, this, &QgisApp::fileSave );
2672   connect( mActionCloseProject, &QAction::triggered, this, &QgisApp::fileClose );
2673   connect( mActionSaveProjectAs, &QAction::triggered, this, &QgisApp::fileSaveAs );
2674   connect( mActionSaveMapAsImage, &QAction::triggered, this, [ = ] { saveMapAsImage(); } );
2675   connect( mActionSaveMapAsPdf, &QAction::triggered, this, [ = ] { saveMapAsPdf(); } );
2676   connect( mActionNewMapCanvas, &QAction::triggered, this, &QgisApp::newMapCanvas );
2677   connect( mActionNew3DMapCanvas, &QAction::triggered, this, &QgisApp::new3DMapCanvas );
2678   connect( mActionNewPrintLayout, &QAction::triggered, this, &QgisApp::newPrintLayout );
2679   connect( mActionNewReport, &QAction::triggered, this, &QgisApp::newReport );
2680   connect( mActionShowLayoutManager, &QAction::triggered, this, &QgisApp::showLayoutManager );
2681   connect( mActionExit, &QAction::triggered, this, &QgisApp::fileExit );
2682   connect( mActionDxfExport, &QAction::triggered, this, &QgisApp::dxfExport );
2683   connect( mActionDwgImport, &QAction::triggered, this, &QgisApp::dwgImport );
2684 
2685   // Edit Menu Items
2686 
2687   connect( mActionUndo, &QAction::triggered, mUndoWidget, &QgsUndoWidget::undo );
2688   connect( mActionRedo, &QAction::triggered, mUndoWidget, &QgsUndoWidget::redo );
2689   connect( mActionCutFeatures, &QAction::triggered, this, [ = ] { cutSelectionToClipboard(); } );
2690   connect( mActionCopyFeatures, &QAction::triggered, this, [ = ] { copySelectionToClipboard(); } );
2691   connect( mActionPasteFeatures, &QAction::triggered, this, [ = ] { pasteFromClipboard(); } );
2692   connect( mActionPasteAsNewVector, &QAction::triggered, this, &QgisApp::pasteAsNewVector );
2693   connect( mActionPasteAsNewMemoryVector, &QAction::triggered, this, [ = ] { pasteAsNewMemoryVector(); } );
2694   connect( mActionCopyStyle, &QAction::triggered, this, [ = ] { copyStyle(); } );
2695   connect( mActionPasteStyle, &QAction::triggered, this, [ = ] { pasteStyle(); } );
2696   connect( mActionCopyLayer, &QAction::triggered, this, &QgisApp::copyLayer );
2697   connect( mActionPasteLayer, &QAction::triggered, this, &QgisApp::pasteLayer );
2698   connect( mActionAddFeature, &QAction::triggered, this, &QgisApp::addFeature );
2699   connect( mActionCircularStringCurvePoint, &QAction::triggered, this, [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::CircularStringCurvePoint ) ); } );
2700   connect( mActionCircularStringRadius, &QAction::triggered, this, [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::CircularStringRadius ) ); } );
2701   connect( mActionCircle2Points, &QAction::triggered, this, [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::Circle2Points ), true ); } );
2702   connect( mActionCircle3Points, &QAction::triggered, this, [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::Circle3Points ), true ); } );
2703   connect( mActionCircle3Tangents, &QAction::triggered, this, [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::Circle3Tangents ), true ); } );
2704   connect( mActionCircle2TangentsPoint, &QAction::triggered, this, [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::Circle2TangentsPoint ), true ); } );
2705   connect( mActionCircleCenterPoint, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::CircleCenterPoint ), true ); } );
2706   connect( mActionEllipseCenter2Points, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::EllipseCenter2Points ), true ); } );
2707   connect( mActionEllipseCenterPoint, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::EllipseCenterPoint ), true ); } );
2708   connect( mActionEllipseExtent, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::EllipseExtent ), true ); } );
2709   connect( mActionEllipseFoci, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::EllipseFoci ), true ); } );
2710   connect( mActionRectangleCenterPoint, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::RectangleCenterPoint ), true ); } );
2711   connect( mActionRectangleExtent, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::RectangleExtent ), true ); } );
2712   connect( mActionRectangle3PointsDistance, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::Rectangle3PointsDistance ), true ); } );
2713   connect( mActionRectangle3PointsProjected, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::Rectangle3PointsProjected ), true ); } );
2714   connect( mActionRegularPolygon2Points, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::RegularPolygon2Points ), true ); } );
2715   connect( mActionRegularPolygonCenterPoint, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::RegularPolygonCenterPoint ), true ); } );
2716   connect( mActionRegularPolygonCenterCorner, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::RegularPolygonCenterCorner ), true ); } );
2717   connect( mActionDigitizeWithCurve, &QAction::triggered, this, &QgisApp::enableDigitizeWithCurve );
2718   connect( mActionStreamDigitize, &QAction::triggered, this, &QgisApp::enableStreamDigitizing );
2719   mActionStreamDigitize->setShortcut( tr( "R", "Keyboard shortcut: toggle stream digitizing" ) );
2720 
2721   connect( mActionMoveFeature, &QAction::triggered, this, &QgisApp::moveFeature );
2722   connect( mActionMoveFeatureCopy, &QAction::triggered, this, &QgisApp::moveFeatureCopy );
2723   connect( mActionRotateFeature, &QAction::triggered, this, &QgisApp::rotateFeature );
2724   connect( mActionScaleFeature, &QAction::triggered, this, &QgisApp::scaleFeature );
2725   connect( mActionReshapeFeatures, &QAction::triggered, this, &QgisApp::reshapeFeatures );
2726   connect( mActionSplitFeatures, &QAction::triggered, this, &QgisApp::splitFeatures );
2727   connect( mActionSplitParts, &QAction::triggered, this, &QgisApp::splitParts );
2728   connect( mActionDeleteSelected, &QAction::triggered, this, [ = ] { deleteSelected( nullptr, nullptr, true ); } );
2729   connect( mActionAddRing, &QAction::triggered, this, &QgisApp::addRing );
2730   connect( mActionFillRing, &QAction::triggered, this, &QgisApp::fillRing );
2731   connect( mActionAddPart, &QAction::triggered, this, &QgisApp::addPart );
2732   connect( mActionSimplifyFeature, &QAction::triggered, this, &QgisApp::simplifyFeature );
2733   connect( mActionDeleteRing, &QAction::triggered, this, &QgisApp::deleteRing );
2734   connect( mActionDeletePart, &QAction::triggered, this, &QgisApp::deletePart );
2735   connect( mActionMergeFeatures, &QAction::triggered, this, &QgisApp::mergeSelectedFeatures );
2736   connect( mActionMergeFeatureAttributes, &QAction::triggered, this, &QgisApp::mergeAttributesOfSelectedFeatures );
2737   connect( mActionMultiEditAttributes, &QAction::triggered, this, &QgisApp::modifyAttributesOfSelectedFeatures );
2738   connect( mActionVertexTool, &QAction::triggered, this, &QgisApp::vertexTool );
2739   connect( mActionVertexToolActiveLayer, &QAction::triggered, this, &QgisApp::vertexToolActiveLayer );
2740   connect( mActionRotatePointSymbols, &QAction::triggered, this, &QgisApp::rotatePointSymbols );
2741   connect( mActionOffsetPointSymbol, &QAction::triggered, this, &QgisApp::offsetPointSymbol );
2742   connect( mActionSnappingOptions, &QAction::triggered, this, &QgisApp::snappingOptions );
2743   connect( mActionOffsetCurve, &QAction::triggered, this, &QgisApp::offsetCurve );
2744   connect( mActionReverseLine, &QAction::triggered, this, &QgisApp::reverseLine );
2745   connect( mActionTrimExtendFeature, &QAction::triggered, this, [ = ] { mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::TrimExtendFeature ) ); } );
2746 
2747   // View Menu Items
2748   connect( mActionPan, &QAction::triggered, this, &QgisApp::pan );
2749   connect( mActionPanToSelected, &QAction::triggered, this, &QgisApp::panToSelected );
2750   connect( mActionZoomIn, &QAction::triggered, this, &QgisApp::zoomIn );
2751   connect( mActionZoomOut, &QAction::triggered, this, &QgisApp::zoomOut );
2752   connect( mActionSelectFeatures, &QAction::triggered, this, &QgisApp::selectFeatures );
2753   connect( mActionSelectPolygon, &QAction::triggered, this, &QgisApp::selectByPolygon );
2754   connect( mActionSelectFreehand, &QAction::triggered, this, &QgisApp::selectByFreehand );
2755   connect( mActionSelectRadius, &QAction::triggered, this, &QgisApp::selectByRadius );
2756   connect( mActionDeselectAll, &QAction::triggered, this, &QgisApp::deselectAll );
2757   connect( mActionDeselectActiveLayer, &QAction::triggered, this, &QgisApp::deselectActiveLayer );
2758   connect( mActionSelectAll, &QAction::triggered, this, &QgisApp::selectAll );
2759   connect( mActionReselect, &QAction::triggered, this, [ = ]
2760   {
2761     QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
2762     if ( !vlayer )
2763     {
2764       visibleMessageBar()->pushMessage(
2765         tr( "No active vector layer" ),
2766         tr( "To reselect features, choose a vector layer in the legend." ),
2767         Qgis::MessageLevel::Info );
2768       return;
2769     }
2770 
2771     vlayer->reselect();
2772   } );
2773   connect( mActionInvertSelection, &QAction::triggered, this, &QgisApp::invertSelection );
2774   connect( mActionSelectByExpression, &QAction::triggered, this, &QgisApp::selectByExpression );
2775   connect( mActionSelectByForm, &QAction::triggered, this, &QgisApp::selectByForm );
2776   connect( mActionIdentify, &QAction::triggered, this, &QgisApp::identify );
2777   connect( mActionFeatureAction, &QAction::triggered, this, &QgisApp::doFeatureAction );
2778   connect( mActionMeasure, &QAction::triggered, this, &QgisApp::measure );
2779   connect( mActionMeasureArea, &QAction::triggered, this, &QgisApp::measureArea );
2780   connect( mActionMeasureAngle, &QAction::triggered, this, &QgisApp::measureAngle );
2781   connect( mActionMeasureBearing, &QAction::triggered, this,  [ = ] { setMapTool( mMapTools->mapTool( QgsAppMapTools::MeasureBearing ) ); } );
2782   connect( mActionZoomFullExtent, &QAction::triggered, this, &QgisApp::zoomFull );
2783   connect( mActionZoomToLayer, &QAction::triggered, this, &QgisApp::zoomToLayerExtent );
2784   connect( mActionZoomToLayers, &QAction::triggered, this, &QgisApp::zoomToLayerExtent );
2785   connect( mActionZoomToSelected, &QAction::triggered, this, &QgisApp::zoomToSelected );
2786   connect( mActionZoomLast, &QAction::triggered, this, &QgisApp::zoomToPrevious );
2787   connect( mActionZoomNext, &QAction::triggered, this, &QgisApp::zoomToNext );
2788   connect( mActionZoomActualSize, &QAction::triggered, this, &QgisApp::zoomActualSize );
2789   connect( mActionMapTips, &QAction::toggled, this, &QgisApp::toggleMapTips );
2790   connect( mActionNewBookmark, &QAction::triggered, this, &QgisApp::newBookmark );
2791   connect( mActionDraw, &QAction::triggered, this, [this] { refreshMapCanvas( true ); } );
2792   connect( mActionTextAnnotation, &QAction::triggered, this, &QgisApp::addTextAnnotation );
2793   connect( mActionFormAnnotation, &QAction::triggered, this, &QgisApp::addFormAnnotation );
2794   connect( mActionHtmlAnnotation, &QAction::triggered, this, &QgisApp::addHtmlAnnotation );
2795   connect( mActionSvgAnnotation, &QAction::triggered, this, &QgisApp::addSvgAnnotation );
2796   connect( mActionAnnotation, &QAction::triggered, this, &QgisApp::modifyAnnotation );
2797   connect( mActionLabeling, &QAction::triggered, this, &QgisApp::labeling );
2798   mStatisticalSummaryDockWidget->setToggleVisibilityAction( mActionStatisticalSummary );
2799 
2800   // Layer Menu Items
2801 
2802   connect( mActionDataSourceManager, &QAction::triggered, this, [ = ]() { dataSourceManager(); } );
2803   connect( mActionNewVectorLayer, &QAction::triggered, this, &QgisApp::newVectorLayer );
2804 #ifdef HAVE_SPATIALITE
2805   connect( mActionNewSpatiaLiteLayer, &QAction::triggered, this, &QgisApp::newSpatialiteLayer );
2806 #endif
2807   connect( mActionNewGeoPackageLayer, &QAction::triggered, this, &QgisApp::newGeoPackageLayer );
2808   connect( mActionNewMemoryLayer, &QAction::triggered, this, &QgisApp::newMemoryLayer );
2809   connect( mActionNewMeshLayer, &QAction::triggered, this, &QgisApp::newMeshLayer );
2810   connect( mActionNewGpxLayer, &QAction::triggered, this, &QgisApp::newGpxLayer );
2811   connect( mActionNewVirtualLayer, &QAction::triggered, this, &QgisApp::addVirtualLayer );
2812   connect( mActionShowRasterCalculator, &QAction::triggered, this, &QgisApp::showRasterCalculator );
2813   connect( mActionShowMeshCalculator, &QAction::triggered, this, &QgisApp::showMeshCalculator );
2814   connect( mActionShowAlignRasterTool, &QAction::triggered, this, &QgisApp::showAlignRasterTool );
2815   connect( mActionEmbedLayers, &QAction::triggered, this, &QgisApp::embedLayers );
2816   connect( mActionAddLayerDefinition, &QAction::triggered, this, &QgisApp::addLayerDefinition );
2817   connect( mActionAddOgrLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "ogr" ) ); } );
2818   connect( mActionAddRasterLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "gdal" ) ); } );
2819   connect( mActionAddMeshLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "mdal" ) ); } );
2820   connect( mActionAddPgLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "postgres" ) ); } );
2821 #ifdef HAVE_SPATIALITE
2822   connect( mActionAddSpatiaLiteLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "spatialite" ) ); } );
2823 #endif
2824   connect( mActionAddMssqlLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "mssql" ) ); } );
2825   connect( mActionAddOracleLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "oracle" ) ); } );
2826   connect( mActionAddHanaLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "hana" ) ); } );
2827   connect( mActionAddWmsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "wms" ) ); } );
2828   connect( mActionAddXyzLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "xyz" ) ); } );
2829   connect( mActionAddVectorTileLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "vectortile" ) ); } );
2830   connect( mActionAddPointCloudLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "ept" ) ); } );
2831   connect( mActionAddWcsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "wcs" ) ); } );
2832 #ifdef HAVE_SPATIALITE
2833   connect( mActionAddWfsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "WFS" ) ); } );
2834 #endif
2835   connect( mActionAddAfsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "arcgisfeatureserver" ) ); } );
2836   connect( mActionAddDelimitedText, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "delimitedtext" ) ); } );
2837   connect( mActionAddVirtualLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "virtual" ) ); } );
2838   connect( mActionOpenTable, &QAction::triggered, this, [ = ]
2839   {
2840     QgsSettings settings;
2841     QgsAttributeTableFilterModel::FilterMode initialMode = settings.enumValue( QStringLiteral( "qgis/attributeTableBehavior" ),  QgsAttributeTableFilterModel::ShowAll );
2842     attributeTable( initialMode );
2843   } );
2844   connect( mActionOpenTableSelected, &QAction::triggered, this, [ = ]
2845   {
2846     attributeTable( QgsAttributeTableFilterModel::ShowSelected );
2847   } );
2848   connect( mActionOpenTableVisible, &QAction::triggered, this, [ = ]
2849   {
2850     attributeTable( QgsAttributeTableFilterModel::ShowVisible );
2851   } );
2852   connect( mActionOpenTableEdited, &QAction::triggered, this, [ = ]
2853   {
2854     attributeTable( QgsAttributeTableFilterModel::ShowEdited );
2855   } );
2856   connect( mActionOpenFieldCalc, &QAction::triggered, this, &QgisApp::fieldCalculator );
2857   connect( mActionToggleEditing, &QAction::triggered, this, [ = ] { toggleEditing(); } );
2858   connect( mActionSaveLayerEdits, &QAction::triggered, this, &QgisApp::saveActiveLayerEdits );
2859   connect( mActionSaveEdits, &QAction::triggered, this, [ = ] { saveEdits(); } );
2860   connect( mActionSaveAllEdits, &QAction::triggered, this, [ = ] { saveAllEdits(); } );
2861   connect( mActionRollbackEdits, &QAction::triggered, this, &QgisApp::rollbackEdits );
2862   connect( mActionRollbackAllEdits, &QAction::triggered, this, [ = ] { rollbackAllEdits(); } );
2863   connect( mActionCancelEdits, &QAction::triggered, this, [ = ] { cancelEdits(); } );
2864   connect( mActionCancelAllEdits, &QAction::triggered, this, [ = ] { cancelAllEdits(); } );
2865   connect( mActionLayerSaveAs, &QAction::triggered, this, [ = ] { saveAsFile(); } );
2866   connect( mActionSaveLayerDefinition, &QAction::triggered, this, &QgisApp::saveAsLayerDefinition );
2867   connect( mActionRemoveLayer, &QAction::triggered, this, &QgisApp::removeLayer );
2868   connect( mActionDuplicateLayer, &QAction::triggered, this, [ = ] { duplicateLayers(); } );
2869   connect( mActionSetLayerScaleVisibility, &QAction::triggered, this, &QgisApp::setLayerScaleVisibility );
2870   connect( mActionSetLayerCRS, &QAction::triggered, this, &QgisApp::setLayerCrs );
2871   connect( mActionSetProjectCRSFromLayer, &QAction::triggered, this, &QgisApp::setProjectCrsFromLayer );
2872   connect( mActionLayerProperties, &QAction::triggered, this, &QgisApp::layerProperties );
2873   connect( mActionLayerSubsetString, &QAction::triggered, this, qOverload<>( &QgisApp::layerSubsetString ) );
2874   connect( mActionAddToOverview, &QAction::triggered, this, &QgisApp::isInOverview );
2875   connect( mActionAddAllToOverview, &QAction::triggered, this, &QgisApp::addAllToOverview );
2876   connect( mActionRemoveAllFromOverview, &QAction::triggered, this, &QgisApp::removeAllFromOverview );
2877   connect( mActionShowAllLayers, &QAction::triggered, this, &QgisApp::showAllLayers );
2878   connect( mActionHideAllLayers, &QAction::triggered, this, &QgisApp::hideAllLayers );
2879   connect( mActionShowSelectedLayers, &QAction::triggered, this, &QgisApp::showSelectedLayers );
2880   connect( mActionHideSelectedLayers, &QAction::triggered, this, &QgisApp::hideSelectedLayers );
2881   connect( mActionToggleSelectedLayers, &QAction::triggered, this, &QgisApp::toggleSelectedLayers );
2882   connect( mActionToggleSelectedLayersIndependently, &QAction::triggered, this, &QgisApp::toggleSelectedLayersIndependently );
2883   connect( mActionHideDeselectedLayers, &QAction::triggered, this, &QgisApp::hideDeselectedLayers );
2884 
2885   // Plugin Menu Items
2886 
2887   connect( mActionManagePlugins, &QAction::triggered, this, &QgisApp::showPluginManager );
2888   connect( mActionShowPythonDialog, &QAction::triggered, this, &QgisApp::showPythonDialog );
2889 
2890   // Settings Menu Items
2891 
2892   connect( mActionToggleFullScreen, &QAction::triggered, this, &QgisApp::toggleFullScreen );
2893   connect( mActionTogglePanelsVisibility, &QAction::triggered, this, &QgisApp::togglePanelsVisibility );
2894   connect( mActionToggleMapOnly, &QAction::triggered, this, &QgisApp::toggleMapOnly );
2895   connect( mActionProjectProperties, &QAction::triggered, this, [ = ] {projectProperties( QString() );} );
2896   connect( mActionOptions, &QAction::triggered, this, &QgisApp::options );
2897   connect( mActionCustomProjection, &QAction::triggered, this, &QgisApp::customProjection );
2898   connect( mActionConfigureShortcuts, &QAction::triggered, this, &QgisApp::configureShortcuts );
2899   connect( mActionStyleManager, &QAction::triggered, this, &QgisApp::showStyleManager );
2900   connect( mActionCustomization, &QAction::triggered, this, &QgisApp::customize );
2901 
2902 #ifdef Q_OS_MAC
2903   // Window Menu Items
2904 
2905   mActionWindowMinimize = new QAction( tr( "Minimize" ), this );
2906   mActionWindowMinimize->setShortcut( tr( "Ctrl+M", "Minimize Window" ) );
2907   mActionWindowMinimize->setStatusTip( tr( "Minimizes the active window to the dock" ) );
2908   connect( mActionWindowMinimize, SIGNAL( triggered() ), this, SLOT( showActiveWindowMinimized() ) );
2909 
2910   mActionWindowZoom = new QAction( tr( "Zoom" ), this );
2911   mActionWindowZoom->setStatusTip( tr( "Toggles between a predefined size and the window size set by the user" ) );
2912   connect( mActionWindowZoom, SIGNAL( triggered() ), this, SLOT( toggleActiveWindowMaximized() ) );
2913 
2914   mActionWindowAllToFront = new QAction( tr( "Bring All to Front" ), this );
2915   mActionWindowAllToFront->setStatusTip( tr( "Bring forward all open windows" ) );
2916   connect( mActionWindowAllToFront, SIGNAL( triggered() ), this, SLOT( bringAllToFront() ) );
2917 
2918   // list of open windows
2919   mWindowActions = new QActionGroup( this );
2920 #endif
2921 
2922   // Vector edits menu
2923   QMenu *menuAllEdits = new QMenu( tr( "Current Edits" ), this );
2924   menuAllEdits->addAction( mActionSaveEdits );
2925   menuAllEdits->addAction( mActionRollbackEdits );
2926   menuAllEdits->addAction( mActionCancelEdits );
2927   menuAllEdits->addSeparator();
2928   menuAllEdits->addAction( mActionSaveAllEdits );
2929   menuAllEdits->addAction( mActionRollbackAllEdits );
2930   menuAllEdits->addAction( mActionCancelAllEdits );
2931   menuAllEdits->setObjectName( "AllEditsMenu" );
2932   mActionAllEdits->setMenu( menuAllEdits );
2933 
2934   // Raster toolbar items
2935   connect( mActionLocalHistogramStretch, &QAction::triggered, this, &QgisApp::localHistogramStretch );
2936   connect( mActionFullHistogramStretch, &QAction::triggered, this, &QgisApp::fullHistogramStretch );
2937   connect( mActionLocalCumulativeCutStretch, &QAction::triggered, this, &QgisApp::localCumulativeCutStretch );
2938   connect( mActionFullCumulativeCutStretch, &QAction::triggered, this, &QgisApp::fullCumulativeCutStretch );
2939   connect( mActionIncreaseBrightness, &QAction::triggered, this, &QgisApp::increaseBrightness );
2940   connect( mActionDecreaseBrightness, &QAction::triggered, this, &QgisApp::decreaseBrightness );
2941   connect( mActionIncreaseContrast, &QAction::triggered, this, &QgisApp::increaseContrast );
2942   connect( mActionDecreaseContrast, &QAction::triggered, this, &QgisApp::decreaseContrast );
2943   connect( mActionIncreaseGamma, &QAction::triggered, this, &QgisApp::increaseGamma );
2944   connect( mActionDecreaseGamma, &QAction::triggered, this, &QgisApp::decreaseGamma );
2945 
2946 #ifdef HAVE_GEOREFERENCER
2947   connect( mActionShowGeoreferencer, &QAction::triggered, this, &QgisApp::showGeoreferencer );
2948 #else
2949   delete mActionShowGeoreferencer;
2950   mActionShowGeoreferencer = nullptr;
2951 #endif
2952 
2953   // Help Menu Items
2954 
2955 #ifdef Q_OS_MAC
2956   mActionHelpContents->setShortcut( QString( "Ctrl+?" ) );
2957   mActionQgisHomePage->setShortcut( QString() );
2958   mActionReportaBug->setShortcut( QString() );
2959 #endif
2960 
2961   mActionHelpContents->setEnabled( QFileInfo::exists( QgsApplication::pkgDataPath() + "/doc/index.html" ) );
2962 
2963   connect( mActionHelpContents, &QAction::triggered, this, &QgisApp::helpContents );
2964   connect( mActionHelpAPI, &QAction::triggered, this, &QgisApp::apiDocumentation );
2965   connect( mActionReportaBug, &QAction::triggered, this, &QgisApp::reportaBug );
2966   connect( mActionNeedSupport, &QAction::triggered, this, &QgisApp::supportProviders );
2967   connect( mActionQgisHomePage, &QAction::triggered, this, &QgisApp::helpQgisHomePage );
2968   connect( mActionCheckQgisVersion, &QAction::triggered, this, &QgisApp::checkQgisVersion );
2969   connect( mActionAbout, &QAction::triggered, this, &QgisApp::about );
2970   connect( mActionSponsors, &QAction::triggered, this, &QgisApp::sponsors );
2971 
2972   connect( mActionShowPinnedLabels, &QAction::toggled, this, &QgisApp::showPinnedLabels );
2973   connect( mActionShowUnplacedLabels, &QAction::toggled, this, [ = ]( bool active )
2974   {
2975     QgsLabelingEngineSettings engineSettings = QgsProject::instance()->labelingEngineSettings();
2976     engineSettings.setFlag( QgsLabelingEngineSettings::DrawUnplacedLabels, active );
2977     QgsProject::instance()->setLabelingEngineSettings( engineSettings );
2978     refreshMapCanvas( true );
2979   } );
2980   connect( QgsProject::instance(), &QgsProject::labelingEngineSettingsChanged, this, [ = ]
2981   {
2982     whileBlocking( mActionShowUnplacedLabels )->setChecked( QgsProject::instance()->labelingEngineSettings().testFlag( QgsLabelingEngineSettings::DrawUnplacedLabels ) );
2983   } );
2984   connect( mActionPinLabels, &QAction::triggered, this, &QgisApp::pinLabels );
2985   connect( mActionShowHideLabels, &QAction::triggered, this, &QgisApp::showHideLabels );
2986   connect( mActionMoveLabel, &QAction::triggered, this, &QgisApp::moveLabel );
2987   connect( mActionRotateLabel, &QAction::triggered, this, &QgisApp::rotateLabel );
2988   connect( mActionChangeLabelProperties, &QAction::triggered, this, &QgisApp::changeLabelProperties );
2989 
2990   connect( mActionDiagramProperties, &QAction::triggered, this, &QgisApp::diagramProperties );
2991 
2992   connect( mActionCreateAnnotationLayer, &QAction::triggered, this, &QgisApp::createAnnotationLayer );
2993   connect( mActionModifyAnnotation, &QAction::triggered, this, [ = ] {  mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::AnnotationEdit ) ); } );
2994   connect( mMainAnnotationLayerProperties, &QAction::triggered, this, [ = ]
2995   {
2996     showLayerProperties( QgsProject::instance()->mainAnnotationLayer() );
2997   } );
2998 
2999   // we can't set the shortcut these actions, because we need to restrict their context to the canvas and it's children..
3000   for ( QWidget *widget :
3001         {
3002           static_cast< QWidget * >( mMapCanvas ),
3003           static_cast< QWidget * >( mLayerTreeView )
3004         } )
3005   {
3006     QShortcut *copyShortcut = new QShortcut( QKeySequence::Copy, widget );
3007     copyShortcut->setContext( Qt::WidgetWithChildrenShortcut );
3008     connect( copyShortcut, &QShortcut::activated, this, [ = ] { copySelectionToClipboard(); } );
3009 
3010     QShortcut *cutShortcut = new QShortcut( QKeySequence::Cut, widget );
3011     cutShortcut->setContext( Qt::WidgetWithChildrenShortcut );
3012     connect( cutShortcut, &QShortcut::activated, this, [ = ] { cutSelectionToClipboard(); } );
3013 
3014     QShortcut *pasteShortcut = new QShortcut( QKeySequence::Paste, widget );
3015     pasteShortcut->setContext( Qt::WidgetWithChildrenShortcut );
3016     connect( pasteShortcut, &QShortcut::activated, this, [ = ] { pasteFromClipboard(); } );
3017 
3018     QShortcut *selectAllShortcut = new QShortcut( QKeySequence::SelectAll, widget );
3019     selectAllShortcut->setContext( Qt::WidgetWithChildrenShortcut );
3020     connect( selectAllShortcut, &QShortcut::activated, this, &QgisApp::selectAll );
3021   }
3022 
3023 #ifndef HAVE_POSTGRESQL
3024   delete mActionAddPgLayer;
3025   mActionAddPgLayer = 0;
3026 #endif
3027 
3028 #ifndef HAVE_ORACLE
3029   delete mActionAddOracleLayer;
3030   mActionAddOracleLayer = nullptr;
3031 #endif
3032 
3033 #ifndef HAVE_HANA
3034   delete mActionAddHanaLayer;
3035   mActionAddHanaLayer = nullptr;
3036 #endif
3037 
3038 }
3039 
showStyleManager()3040 void QgisApp::showStyleManager()
3041 {
3042   QgsGui::windowManager()->openStandardDialog( QgsWindowManagerInterface::DialogStyleManager );
3043 }
3044 
initPythonConsoleOptions()3045 void QgisApp::initPythonConsoleOptions()
3046 {
3047   QgsPythonRunner::run( QStringLiteral( "import console" ) );
3048   QgsPythonRunner::run( QStringLiteral( "console.init_options_widget()" ) );
3049 }
3050 
showPythonDialog()3051 void QgisApp::showPythonDialog()
3052 {
3053 #ifdef WITH_BINDINGS
3054   if ( !mPythonUtils || !mPythonUtils->isEnabled() )
3055     return;
3056 
3057   bool res = mPythonUtils->runString(
3058                "import console\n"
3059                "console.show_console()\n", tr( "Failed to open Python console:" ), false );
3060 
3061   if ( !res )
3062   {
3063     QString className, text;
3064     mPythonUtils->getError( className, text );
3065     visibleMessageBar()->pushMessage( tr( "Error" ), tr( "Failed to open Python console:" ) + '\n' + className + ": " + text, Qgis::MessageLevel::Warning );
3066   }
3067 #ifdef Q_OS_MAC
3068   else
3069   {
3070     addWindow( mActionShowPythonDialog );
3071   }
3072 #endif
3073 #endif
3074 }
3075 
createActionGroups()3076 void QgisApp::createActionGroups()
3077 {
3078   //
3079   // Map Tool Group
3080   mMapToolGroup = new QActionGroup( this );
3081   mMapToolGroup->addAction( mActionPan );
3082   mMapToolGroup->addAction( mActionZoomIn );
3083   mMapToolGroup->addAction( mActionZoomOut );
3084   mMapToolGroup->addAction( mActionIdentify );
3085   mMapToolGroup->addAction( mActionFeatureAction );
3086   mMapToolGroup->addAction( mActionSelectFeatures );
3087   mMapToolGroup->addAction( mActionSelectPolygon );
3088   mMapToolGroup->addAction( mActionSelectFreehand );
3089   mMapToolGroup->addAction( mActionSelectRadius );
3090   mMapToolGroup->addAction( mActionDeselectAll );
3091   mMapToolGroup->addAction( mActionDeselectActiveLayer );
3092   mMapToolGroup->addAction( mActionSelectAll );
3093   mMapToolGroup->addAction( mActionReselect );
3094   mMapToolGroup->addAction( mActionInvertSelection );
3095   mMapToolGroup->addAction( mActionMeasure );
3096   mMapToolGroup->addAction( mActionMeasureArea );
3097   mMapToolGroup->addAction( mActionMeasureAngle );
3098   mMapToolGroup->addAction( mActionMeasureBearing );
3099   mMapToolGroup->addAction( mActionAddFeature );
3100   mMapToolGroup->addAction( mActionCircularStringCurvePoint );
3101   mMapToolGroup->addAction( mActionCircularStringRadius );
3102   mMapToolGroup->addAction( mActionCircle2Points );
3103   mMapToolGroup->addAction( mActionCircle3Points );
3104   mMapToolGroup->addAction( mActionCircle3Tangents );
3105   mMapToolGroup->addAction( mActionCircle2TangentsPoint );
3106   mMapToolGroup->addAction( mActionCircleCenterPoint );
3107   mMapToolGroup->addAction( mActionEllipseCenter2Points );
3108   mMapToolGroup->addAction( mActionEllipseCenterPoint );
3109   mMapToolGroup->addAction( mActionEllipseExtent );
3110   mMapToolGroup->addAction( mActionEllipseFoci );
3111   mMapToolGroup->addAction( mActionRectangleCenterPoint );
3112   mMapToolGroup->addAction( mActionRectangleExtent );
3113   mMapToolGroup->addAction( mActionRectangle3PointsDistance );
3114   mMapToolGroup->addAction( mActionRectangle3PointsProjected );
3115   mMapToolGroup->addAction( mActionRegularPolygon2Points );
3116   mMapToolGroup->addAction( mActionRegularPolygonCenterPoint );
3117   mMapToolGroup->addAction( mActionRegularPolygonCenterCorner );
3118   mMapToolGroup->addAction( mActionMoveFeature );
3119   mMapToolGroup->addAction( mActionMoveFeatureCopy );
3120   mMapToolGroup->addAction( mActionRotateFeature );
3121   mMapToolGroup->addAction( mActionScaleFeature );
3122   mMapToolGroup->addAction( mActionOffsetCurve );
3123   mMapToolGroup->addAction( mActionReshapeFeatures );
3124   mMapToolGroup->addAction( mActionSplitFeatures );
3125   mMapToolGroup->addAction( mActionSplitParts );
3126   mMapToolGroup->addAction( mActionDeleteSelected );
3127   mMapToolGroup->addAction( mActionAddRing );
3128   mMapToolGroup->addAction( mActionFillRing );
3129   mMapToolGroup->addAction( mActionAddPart );
3130   mMapToolGroup->addAction( mActionSimplifyFeature );
3131   mMapToolGroup->addAction( mActionDeleteRing );
3132   mMapToolGroup->addAction( mActionDeletePart );
3133   mMapToolGroup->addAction( mActionMergeFeatures );
3134   mMapToolGroup->addAction( mActionMergeFeatureAttributes );
3135   mMapToolGroup->addAction( mActionVertexTool );
3136   mMapToolGroup->addAction( mActionVertexToolActiveLayer );
3137   mMapToolGroup->addAction( mActionRotatePointSymbols );
3138   mMapToolGroup->addAction( mActionOffsetPointSymbol );
3139   mMapToolGroup->addAction( mActionPinLabels );
3140   mMapToolGroup->addAction( mActionShowHideLabels );
3141   mMapToolGroup->addAction( mActionMoveLabel );
3142   mMapToolGroup->addAction( mActionRotateLabel );
3143   mMapToolGroup->addAction( mActionChangeLabelProperties );
3144   mMapToolGroup->addAction( mActionReverseLine );
3145   mMapToolGroup->addAction( mActionTrimExtendFeature );
3146   mMapToolGroup->addAction( mActionModifyAnnotation );
3147 
3148   //
3149   // Preview Modes Group
3150   QActionGroup *mPreviewGroup = new QActionGroup( this );
3151   mPreviewGroup->setExclusive( true );
3152   mActionPreviewModeOff->setActionGroup( mPreviewGroup );
3153   mActionPreviewModeMono->setActionGroup( mPreviewGroup );
3154   mActionPreviewModeGrayscale->setActionGroup( mPreviewGroup );
3155   mActionPreviewProtanope->setActionGroup( mPreviewGroup );
3156   mActionPreviewDeuteranope->setActionGroup( mPreviewGroup );
3157   mActionPreviewTritanope->setActionGroup( mPreviewGroup );
3158 }
3159 
setAppStyleSheet(const QString & stylesheet)3160 void QgisApp::setAppStyleSheet( const QString &stylesheet )
3161 {
3162   // avoid crash on stylesheet change -- see https://bugreports.qt.io/browse/QTBUG-69204
3163   static bool sOnce = false;
3164   if ( sOnce )
3165     return;
3166   sOnce = true;
3167 
3168   setStyleSheet( stylesheet );
3169 
3170   // cascade styles to any current layout designers
3171   const auto constMLayoutDesignerDialogs = mLayoutDesignerDialogs;
3172   for ( QgsLayoutDesignerDialog *d : constMLayoutDesignerDialogs )
3173   {
3174     d->setStyleSheet( stylesheet );
3175   }
3176 
3177   if ( mpMaptip )
3178   {
3179     mpMaptip->applyFontSettings();
3180   }
3181 }
3182 
createMenus()3183 void QgisApp::createMenus()
3184 {
3185   /*
3186    * The User Interface Guidelines for each platform specify different locations
3187    * for the following items.
3188    *
3189    * Custom CRS, Options:
3190    * Gnome - bottom of Edit menu
3191    * Mac - Application menu (moved automatically by Qt)
3192    * Kde, Win - Settings menu (Win should use Tools menu but we don't have one)
3193    *
3194    * Panel and Toolbar submenus, Toggle Full Screen:
3195    * Gnome, Mac, Win - View menu
3196    * Kde - Settings menu
3197    *
3198    * For Mac, About and Exit are also automatically moved by Qt to the Application menu.
3199    */
3200 
3201   // Layer menu
3202 
3203   // Panel and Toolbar Submenus
3204   mPanelMenu = new QMenu( tr( "Panels" ), this );
3205   mPanelMenu->setObjectName( QStringLiteral( "mPanelMenu" ) );
3206   mToolbarMenu = new QMenu( tr( "Toolbars" ), this );
3207   mToolbarMenu->setObjectName( QStringLiteral( "mToolbarMenu" ) );
3208 
3209   // Get platform for menu layout customization (Gnome, Kde, Mac, Win)
3210   QDialogButtonBox::ButtonLayout layout =
3211     QDialogButtonBox::ButtonLayout( style()->styleHint( QStyle::SH_DialogButtonLayout, nullptr, this ) );
3212 
3213   // Connect once for the entire submenu.
3214   connect( mRecentProjectsMenu, &QMenu::triggered, this, static_cast < void ( QgisApp::* )( QAction *action ) >( &QgisApp::openProject ) );
3215   connect( mProjectFromTemplateMenu, &QMenu::triggered,
3216            this, &QgisApp::fileNewFromTemplateAction );
3217 
3218 
3219   // View Menu
3220 
3221   if ( layout != QDialogButtonBox::KdeLayout )
3222   {
3223     mViewMenu->addSeparator();
3224     mViewMenu->addMenu( mPanelMenu );
3225     mViewMenu->addMenu( mToolbarMenu );
3226     mViewMenu->addAction( mActionToggleFullScreen );
3227     mViewMenu->addAction( mActionTogglePanelsVisibility );
3228     mViewMenu->addAction( mActionToggleMapOnly );
3229   }
3230   else
3231   {
3232     // on the top of the settings menu
3233     QAction *before = mSettingsMenu->actions().at( 0 );
3234     mSettingsMenu->insertMenu( before, mPanelMenu );
3235     mSettingsMenu->insertMenu( before, mToolbarMenu );
3236     mSettingsMenu->insertAction( before, mActionToggleFullScreen );
3237     mSettingsMenu->insertAction( before, mActionTogglePanelsVisibility );
3238     mSettingsMenu->insertAction( before, mActionToggleMapOnly );
3239     mSettingsMenu->insertSeparator( before );
3240   }
3241 
3242 #ifdef Q_OS_MAC
3243 
3244   // keep plugins from hijacking About and Preferences application menus
3245   // these duplicate actions will be moved to application menus by Qt
3246   mProjectMenu->addAction( mActionAbout );
3247   QAction *actionPrefs = new QAction( tr( "Preferences…" ), this );
3248   actionPrefs->setMenuRole( QAction::PreferencesRole );
3249   actionPrefs->setIcon( mActionOptions->icon() );
3250   connect( actionPrefs, &QAction::triggered, this, &QgisApp::options );
3251   mProjectMenu->addAction( actionPrefs );
3252 
3253   // Window Menu
3254 
3255   mWindowMenu = new QMenu( tr( "Window" ), this );
3256 
3257   mWindowMenu->addAction( mActionWindowMinimize );
3258   mWindowMenu->addAction( mActionWindowZoom );
3259   mWindowMenu->addSeparator();
3260 
3261   mWindowMenu->addAction( mActionWindowAllToFront );
3262   mWindowMenu->addSeparator();
3263 
3264   // insert before Help menu, as per Mac OS convention
3265   menuBar()->insertMenu( mHelpMenu->menuAction(), mWindowMenu );
3266 #endif
3267 
3268   // Database Menu
3269   // don't add it yet, wait for a plugin
3270   mDatabaseMenu = new QMenu( tr( "&Database" ), menuBar() );
3271   mDatabaseMenu->setObjectName( QStringLiteral( "mDatabaseMenu" ) );
3272   // Web Menu
3273   // don't add it yet, wait for a plugin
3274   mWebMenu = new QMenu( tr( "&Web" ), menuBar() );
3275   mWebMenu->setObjectName( QStringLiteral( "mWebMenu" ) );
3276 
3277   createProfileMenu();
3278 }
3279 
refreshProfileMenu()3280 void QgisApp::refreshProfileMenu()
3281 {
3282   if ( !mConfigMenu )
3283     return;
3284 
3285   mConfigMenu->clear();
3286   QgsUserProfile *profile = userProfileManager()->userProfile();
3287   QString activeName = profile->name();
3288   mConfigMenu->setTitle( tr( "&User Profiles" ) );
3289 
3290   QActionGroup *profileGroup = new QActionGroup( mConfigMenu );
3291   profileGroup->setExclusive( true );
3292 
3293   const auto constAllProfiles = userProfileManager()->allProfiles();
3294   for ( const QString &name : constAllProfiles )
3295   {
3296     std::unique_ptr< QgsUserProfile > namedProfile( userProfileManager()->profileForName( name ) );
3297     QAction *action = new QAction( namedProfile->icon(), namedProfile->alias(), profileGroup );
3298     action->setToolTip( namedProfile->folder() );
3299     action->setCheckable( true );
3300     action->setObjectName( "mActionProfile_" + namedProfile->alias() );
3301     mConfigMenu->addAction( action );
3302 
3303     if ( name == activeName )
3304     {
3305       action->setChecked( true );
3306     }
3307     else
3308     {
3309       connect( action, &QAction::triggered, this, [this, name]()
3310       {
3311         userProfileManager()->loadUserProfile( name );
3312       } );
3313     }
3314   }
3315 
3316   mConfigMenu->addSeparator( );
3317 
3318   QAction *openProfileFolderAction = mConfigMenu->addAction( tr( "Open Active Profile Folder" ) );
3319   openProfileFolderAction->setObjectName( "mActionOpenActiveProfileFolder" );
3320   connect( openProfileFolderAction, &QAction::triggered, this, [this]()
3321   {
3322     QDesktopServices::openUrl( QUrl::fromLocalFile( userProfileManager()->userProfile()->folder() ) );
3323   } );
3324 
3325   QAction *newProfileAction = mConfigMenu->addAction( tr( "New Profile…" ) );
3326   newProfileAction->setObjectName( "mActionNewProfile" );
3327   connect( newProfileAction, &QAction::triggered, this, &QgisApp::newProfile );
3328 }
3329 
createProfileMenu()3330 void QgisApp::createProfileMenu()
3331 {
3332   mConfigMenu = new QMenu( this );
3333   mConfigMenu->setObjectName( "mUserProfileMenu" );
3334 
3335   settingsMenu()->insertMenu( settingsMenu()->actions().first(), mConfigMenu );
3336 
3337   refreshProfileMenu();
3338 }
3339 
createToolBars()3340 void QgisApp::createToolBars()
3341 {
3342   QgsSettings settings;
3343   // QSize myIconSize ( 32,32 ); //large icons
3344   // Note: we need to set each object name to ensure that
3345   // qmainwindow::saveState and qmainwindow::restoreState
3346   // work properly
3347 
3348   QList<QToolBar *> toolbarMenuToolBars;
3349   toolbarMenuToolBars << mFileToolBar
3350                       << mDataSourceManagerToolBar
3351                       << mLayerToolBar
3352                       << mDigitizeToolBar
3353                       << mAdvancedDigitizeToolBar
3354                       << mShapeDigitizeToolBar
3355                       << mMapNavToolBar
3356                       << mAttributesToolBar
3357                       << mSelectionToolBar
3358                       << mPluginToolBar
3359                       << mHelpToolBar
3360                       << mRasterToolBar
3361                       << mVectorToolBar
3362                       << mDatabaseToolBar
3363                       << mWebToolBar
3364                       << mLabelToolBar
3365                       << mSnappingToolBar
3366                       << mMeshToolBar
3367                       << mAnnotationsToolBar;
3368 
3369   mSnappingWidget = new QgsSnappingWidget( QgsProject::instance(), mMapCanvas, mSnappingToolBar );
3370   mSnappingWidget->setObjectName( QStringLiteral( "mSnappingWidget" ) );
3371   connect( mSnappingWidget, &QgsSnappingWidget::snappingConfigChanged, QgsProject::instance(), [ = ] { QgsProject::instance()->setSnappingConfig( mSnappingWidget->config() ); } );
3372   mSnappingToolBar->addWidget( mSnappingWidget );
3373 
3374   mTracer = new QgsMapCanvasTracer( mMapCanvas, messageBar() );
3375   mTracer->setActionEnableTracing( mSnappingWidget->enableTracingAction() );
3376   mTracer->setActionEnableSnapping( mSnappingWidget->enableSnappingAction() );
3377   connect( mSnappingWidget->tracingOffsetSpinBox(),
3378            static_cast< void ( QgsDoubleSpinBox::* )( double ) >( &QgsDoubleSpinBox::valueChanged ),
3379   this, [ = ]( double v ) { mTracer->setOffset( v ); } );
3380 
3381   mDigitizeModeToolButton = new QToolButton();
3382   mDigitizeModeToolButton->setPopupMode( QToolButton::MenuButtonPopup );
3383   QMenu *digitizeMenu = new QMenu( mDigitizeModeToolButton );
3384   digitizeMenu->addAction( mActionDigitizeWithCurve );
3385   digitizeMenu->addAction( mActionStreamDigitize );
3386   digitizeMenu->addSeparator();
3387   digitizeMenu->addAction( mMapTools->streamDigitizingSettingsAction() );
3388   mDigitizeModeToolButton->setMenu( digitizeMenu );
3389 
3390   switch ( settings.value( QStringLiteral( "UI/digitizeTechnique" ), 0 ).toInt() )
3391   {
3392     case 0:
3393       mDigitizeModeToolButton->setDefaultAction( mActionDigitizeWithCurve );
3394       break;
3395     case 1:
3396       mDigitizeModeToolButton->setDefaultAction( mActionStreamDigitize );
3397       break;
3398   }
3399   mAdvancedDigitizeToolBar->insertWidget( mAdvancedDigitizeToolBar->actions().at( 0 ), mDigitizeModeToolButton );
3400 
3401   QList<QAction *> toolbarMenuActions;
3402   // Set action names so that they can be used in customization
3403   const auto constToolbarMenuToolBars = toolbarMenuToolBars;
3404   for ( QToolBar *toolBar : constToolbarMenuToolBars )
3405   {
3406     toolBar->toggleViewAction()->setObjectName( "mActionToggle" + toolBar->objectName().mid( 1 ) );
3407     toolbarMenuActions << toolBar->toggleViewAction();
3408   }
3409 
3410   // sort actions in toolbar menu
3411   std::sort( toolbarMenuActions.begin(), toolbarMenuActions.end(), cmpByText_ );
3412 
3413   mToolbarMenu->addActions( toolbarMenuActions );
3414 
3415   // advanced selection tool button
3416   QToolButton *bt = new QToolButton( mSelectionToolBar );
3417   bt->setPopupMode( QToolButton::MenuButtonPopup );
3418   bt->addAction( mActionSelectByForm );
3419   bt->addAction( mActionSelectByExpression );
3420   bt->addAction( mActionSelectAll );
3421   bt->addAction( mActionInvertSelection );
3422 
3423   QAction *defAdvancedSelectionAction = mActionSelectByForm;
3424   switch ( settings.value( QStringLiteral( "UI/selectionTool" ), 0 ).toInt() )
3425   {
3426     case 0:
3427       defAdvancedSelectionAction = mActionSelectByForm;
3428       break;
3429     case 1:
3430       defAdvancedSelectionAction = mActionSelectByExpression;
3431       break;
3432     case 2:
3433       defAdvancedSelectionAction = mActionSelectAll;
3434       break;
3435     case 3:
3436       defAdvancedSelectionAction = mActionInvertSelection;
3437       break;
3438   }
3439   bt->setDefaultAction( defAdvancedSelectionAction );
3440   QAction *advancedSelectionAction = mSelectionToolBar->insertWidget( mActionOpenTable, bt );
3441   advancedSelectionAction->setObjectName( QStringLiteral( "ActionSelection" ) );
3442   connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3443 
3444   // mouse select tool button
3445   bt = new QToolButton( mSelectionToolBar );
3446   bt->setPopupMode( QToolButton::MenuButtonPopup );
3447   bt->addAction( mActionSelectFeatures );
3448   bt->addAction( mActionSelectPolygon );
3449   bt->addAction( mActionSelectFreehand );
3450   bt->addAction( mActionSelectRadius );
3451 
3452   QAction *defMouseSelectAction = mActionSelectFeatures;
3453   switch ( settings.value( QStringLiteral( "UI/selectTool" ), 1 ).toInt() )
3454   {
3455     case 1:
3456       defMouseSelectAction = mActionSelectFeatures;
3457       break;
3458     case 2:
3459       defMouseSelectAction = mActionSelectRadius;
3460       break;
3461     case 3:
3462       defMouseSelectAction = mActionSelectPolygon;
3463       break;
3464     case 4:
3465       defMouseSelectAction = mActionSelectFreehand;
3466       break;
3467   }
3468   bt->setDefaultAction( defMouseSelectAction );
3469   QAction *mouseSelectionAction = mSelectionToolBar->insertWidget( advancedSelectionAction, bt );
3470   mouseSelectionAction->setObjectName( QStringLiteral( "ActionSelect" ) );
3471   connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3472 
3473   // deselection tool button
3474   bt = new QToolButton( mSelectionToolBar );
3475   bt->setPopupMode( QToolButton::MenuButtonPopup );
3476   bt->addAction( mActionDeselectAll );
3477   bt->addAction( mActionDeselectActiveLayer );
3478 
3479   QAction *defDeselectionAction = mActionDeselectAll;
3480   switch ( settings.value( QStringLiteral( "UI/deselectionTool" ), 0 ).toInt() )
3481   {
3482     case 0:
3483       defDeselectionAction = mActionDeselectAll;
3484       break;
3485     case 1:
3486       defDeselectionAction = mActionDeselectActiveLayer;
3487       break;
3488   }
3489   bt->setDefaultAction( defDeselectionAction );
3490   QAction *deselectionAction = mSelectionToolBar->insertWidget( mActionOpenTable, bt );
3491   deselectionAction->setObjectName( QStringLiteral( "ActionDeselection" ) );
3492   connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3493 
3494   // feature action tool button
3495   bt = new QToolButton( mAttributesToolBar );
3496   bt->setPopupMode( QToolButton::MenuButtonPopup );
3497   bt->setDefaultAction( mActionFeatureAction );
3498   mFeatureActionMenu = new QMenu( bt );
3499   connect( mFeatureActionMenu, &QMenu::triggered, this, &QgisApp::updateDefaultFeatureAction );
3500   connect( mFeatureActionMenu, &QMenu::triggered, this, &QgisApp::doFeatureAction );
3501   connect( mFeatureActionMenu, &QMenu::aboutToShow, this, &QgisApp::refreshFeatureActions );
3502   bt->setMenu( mFeatureActionMenu );
3503   QAction *featureActionAction = mAttributesToolBar->insertWidget( mouseSelectionAction, bt );
3504   featureActionAction->setObjectName( QStringLiteral( "ActionFeatureAction" ) );
3505 
3506 
3507 
3508   // open table tool button
3509 
3510   bt = new QToolButton( mAttributesToolBar );
3511   bt->setPopupMode( QToolButton::MenuButtonPopup );
3512   bt->addAction( mActionOpenTable );
3513   bt->addAction( mActionOpenTableSelected );
3514   bt->addAction( mActionOpenTableVisible );
3515   bt->addAction( mActionOpenTableEdited );
3516 
3517   QAction *defOpenTableAction = mActionOpenTable;
3518   switch ( settings.value( QStringLiteral( "UI/openTableTool" ), 0 ).toInt() )
3519   {
3520     case 0:
3521       defOpenTableAction = mActionOpenTable;
3522       break;
3523     case 1:
3524       defOpenTableAction = mActionOpenTableSelected;
3525       break;
3526     case 2:
3527       defOpenTableAction = mActionOpenTableVisible;
3528       break;
3529     case 3:
3530       defOpenTableAction = mActionOpenTableEdited;
3531       break;
3532   }
3533   bt->setDefaultAction( defOpenTableAction );
3534   QAction *openTableAction = mAttributesToolBar->insertWidget( mActionMapTips, bt );
3535   openTableAction->setObjectName( QStringLiteral( "ActionOpenTable" ) );
3536   connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3537 
3538 
3539 
3540   // measure tool button
3541 
3542   bt = new QToolButton( mAttributesToolBar );
3543   bt->setPopupMode( QToolButton::MenuButtonPopup );
3544   bt->addAction( mActionMeasure );
3545   bt->addAction( mActionMeasureArea );
3546   bt->addAction( mActionMeasureBearing );
3547   bt->addAction( mActionMeasureAngle );
3548 
3549   QAction *defMeasureAction = mActionMeasure;
3550   switch ( settings.value( QStringLiteral( "UI/measureTool" ), 0 ).toInt() )
3551   {
3552     case 0:
3553       defMeasureAction = mActionMeasure;
3554       break;
3555     case 1:
3556       defMeasureAction = mActionMeasureArea;
3557       break;
3558     case 2:
3559       defMeasureAction = mActionMeasureBearing;
3560       break;
3561     case 3:
3562       defMeasureAction = mActionMeasureAngle;
3563       break;
3564   }
3565   bt->setDefaultAction( defMeasureAction );
3566   QAction *measureAction = mAttributesToolBar->insertWidget( mActionMapTips, bt );
3567   measureAction->setObjectName( QStringLiteral( "ActionMeasure" ) );
3568   connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3569 
3570   // vector layer edits tool buttons
3571   QToolButton *tbAllEdits = qobject_cast<QToolButton *>( mDigitizeToolBar->widgetForAction( mActionAllEdits ) );
3572   tbAllEdits->setPopupMode( QToolButton::InstantPopup );
3573 
3574   // new layer tool button
3575 
3576   bt = new QToolButton();
3577   bt->setPopupMode( QToolButton::MenuButtonPopup );
3578   bt->addAction( mActionNewVectorLayer );
3579 #ifdef HAVE_SPATIALITE
3580   bt->addAction( mActionNewSpatiaLiteLayer );
3581 #endif
3582   bt->addAction( mActionNewGeoPackageLayer );
3583   bt->addAction( mActionNewMemoryLayer );
3584 
3585   QAction *defNewLayerAction = mActionNewVectorLayer;
3586   switch ( settings.value( QStringLiteral( "UI/defaultNewLayer" ), 1 ).toInt() )
3587   {
3588     case 0:
3589       defNewLayerAction = mActionNewSpatiaLiteLayer;
3590       break;
3591     case 1:
3592       defNewLayerAction = mActionNewVectorLayer;
3593       break;
3594     case 2:
3595       defNewLayerAction = mActionNewMemoryLayer;
3596       break;
3597     case 3:
3598       defNewLayerAction = mActionNewGeoPackageLayer;
3599       break;
3600   }
3601   bt->setDefaultAction( defNewLayerAction );
3602   QAction *newLayerAction = mLayerToolBar->addWidget( bt );
3603 
3604   newLayerAction->setObjectName( QStringLiteral( "ActionNewLayer" ) );
3605   connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3606 
3607   // add db layer button
3608   bt = new QToolButton();
3609   bt->setPopupMode( QToolButton::MenuButtonPopup );
3610   if ( mActionAddPgLayer )
3611     bt->addAction( mActionAddPgLayer );
3612   if ( mActionAddMssqlLayer )
3613     bt->addAction( mActionAddMssqlLayer );
3614   if ( mActionAddOracleLayer )
3615     bt->addAction( mActionAddOracleLayer );
3616   if ( mActionAddHanaLayer )
3617     bt->addAction( mActionAddHanaLayer );
3618   QAction *defAddDbLayerAction = mActionAddPgLayer;
3619   switch ( settings.value( QStringLiteral( "UI/defaultAddDbLayerAction" ), 0 ).toInt() )
3620   {
3621     case 0:
3622       defAddDbLayerAction = mActionAddPgLayer;
3623       break;
3624     case 1:
3625       defAddDbLayerAction = mActionAddMssqlLayer;
3626       break;
3627     case 2:
3628       defAddDbLayerAction = mActionAddOracleLayer;
3629       break;
3630     case 3:
3631       defAddDbLayerAction = mActionAddHanaLayer;
3632       break;
3633   }
3634   if ( defAddDbLayerAction )
3635     bt->setDefaultAction( defAddDbLayerAction );
3636   QAction *addDbLayerAction = mLayerToolBar->insertWidget( mActionAddWmsLayer, bt );
3637   addDbLayerAction->setObjectName( QStringLiteral( "ActionAddDbLayer" ) );
3638   connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3639 
3640   QLayout *layout = mLayerToolBar->layout();
3641   for ( int i = 0; i < layout->count(); ++i )
3642   {
3643     layout->itemAt( i )->setAlignment( Qt::AlignLeft );
3644   }
3645 
3646   //circular string digitize tool button
3647   QToolButton *tbAddCircularString = new QToolButton( mShapeDigitizeToolBar );
3648   tbAddCircularString->setPopupMode( QToolButton::MenuButtonPopup );
3649   tbAddCircularString->addAction( mActionCircularStringCurvePoint );
3650   tbAddCircularString->addAction( mActionCircularStringRadius );
3651   QAction *defActionCircularString = mActionCircularStringCurvePoint;
3652   switch ( settings.value( QStringLiteral( "UI/defaultCircularString" ), 0 ).toInt() )
3653   {
3654     case 0:
3655       defActionCircularString = mActionCircularStringCurvePoint;
3656       break;
3657     case 1:
3658       defActionCircularString = mActionCircularStringRadius;
3659       break;
3660   }
3661   tbAddCircularString->setDefaultAction( defActionCircularString );
3662   QAction *addCircularAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddCircularString );
3663   addCircularAction->setObjectName( QStringLiteral( "ActionAddCircularString" ) );
3664   connect( tbAddCircularString, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3665 
3666   //circle digitize tool button
3667   QToolButton *tbAddCircle = new QToolButton( mShapeDigitizeToolBar );
3668   tbAddCircle->setPopupMode( QToolButton::MenuButtonPopup );
3669   tbAddCircle->addAction( mActionCircle2Points );
3670   tbAddCircle->addAction( mActionCircle3Points );
3671   tbAddCircle->addAction( mActionCircle3Tangents );
3672   tbAddCircle->addAction( mActionCircle2TangentsPoint );
3673   tbAddCircle->addAction( mActionCircleCenterPoint );
3674   QAction *defActionCircle = mActionCircle2Points;
3675   switch ( settings.value( QStringLiteral( "UI/defaultCircle" ), 0 ).toInt() )
3676   {
3677     case 0:
3678       defActionCircle = mActionCircle2Points;
3679       break;
3680     case 1:
3681       defActionCircle = mActionCircle3Points;
3682       break;
3683     case 2:
3684       defActionCircle = mActionCircle3Tangents;
3685       break;
3686     case 3:
3687       defActionCircle = mActionCircle2TangentsPoint;
3688       break;
3689     case 4:
3690       defActionCircle = mActionCircleCenterPoint;
3691       break;
3692   }
3693   tbAddCircle->setDefaultAction( defActionCircle );
3694   QAction *addCircleAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddCircle );
3695   addCircleAction->setObjectName( QStringLiteral( "ActionAddCircle" ) );
3696   connect( tbAddCircle, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3697 
3698   //ellipse digitize tool button
3699   QToolButton *tbAddEllipse = new QToolButton( mShapeDigitizeToolBar );
3700   tbAddEllipse->setPopupMode( QToolButton::MenuButtonPopup );
3701   tbAddEllipse->addAction( mActionEllipseCenter2Points );
3702   tbAddEllipse->addAction( mActionEllipseCenterPoint );
3703   tbAddEllipse->addAction( mActionEllipseExtent );
3704   tbAddEllipse->addAction( mActionEllipseFoci );
3705   QAction *defActionEllipse = mActionEllipseCenter2Points;
3706   switch ( settings.value( QStringLiteral( "UI/defaultEllipse" ), 0 ).toInt() )
3707   {
3708     case 0:
3709       defActionEllipse = mActionEllipseCenter2Points;
3710       break;
3711     case 1:
3712       defActionEllipse = mActionEllipseCenterPoint;
3713       break;
3714     case 2:
3715       defActionEllipse = mActionEllipseExtent;
3716       break;
3717     case 3:
3718       defActionEllipse = mActionEllipseFoci;
3719       break;
3720   }
3721   tbAddEllipse->setDefaultAction( defActionEllipse );
3722   QAction *addEllipseAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddEllipse );
3723   addEllipseAction->setObjectName( QStringLiteral( "ActionAddEllipse" ) );
3724   connect( tbAddEllipse, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3725 
3726   //Rectangle digitize tool button
3727   QToolButton *tbAddRectangle = new QToolButton( mShapeDigitizeToolBar );
3728   tbAddRectangle->setPopupMode( QToolButton::MenuButtonPopup );
3729   tbAddRectangle->addAction( mActionRectangleCenterPoint );
3730   tbAddRectangle->addAction( mActionRectangleExtent );
3731   tbAddRectangle->addAction( mActionRectangle3PointsDistance );
3732   tbAddRectangle->addAction( mActionRectangle3PointsProjected );
3733   QAction *defActionRectangle = mActionRectangleCenterPoint;
3734   switch ( settings.value( QStringLiteral( "UI/defaultRectangle" ), 0 ).toInt() )
3735   {
3736     case 0:
3737       defActionRectangle = mActionRectangleCenterPoint;
3738       break;
3739     case 1:
3740       defActionRectangle = mActionRectangleExtent;
3741       break;
3742     case 2:
3743       defActionRectangle = mActionRectangle3PointsDistance;
3744       break;
3745     case 3:
3746       defActionRectangle = mActionRectangle3PointsProjected;
3747       break;
3748   }
3749   tbAddRectangle->setDefaultAction( defActionRectangle );
3750   QAction *addRectangleAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddRectangle );
3751   addRectangleAction->setObjectName( QStringLiteral( "ActionAddRectangle" ) );
3752   connect( tbAddRectangle, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3753 
3754   //Regular polygon digitize tool button
3755   QToolButton *tbAddRegularPolygon = new QToolButton( mShapeDigitizeToolBar );
3756   tbAddRegularPolygon->setPopupMode( QToolButton::MenuButtonPopup );
3757   tbAddRegularPolygon->addAction( mActionRegularPolygon2Points );
3758   tbAddRegularPolygon->addAction( mActionRegularPolygonCenterPoint );
3759   tbAddRegularPolygon->addAction( mActionRegularPolygonCenterCorner );
3760   QAction *defActionRegularPolygon = mActionRegularPolygon2Points;
3761   switch ( settings.value( QStringLiteral( "UI/defaultRegularPolygon" ), 0 ).toInt() )
3762   {
3763     case 0:
3764       defActionRegularPolygon = mActionRegularPolygon2Points;
3765       break;
3766     case 1:
3767       defActionRegularPolygon = mActionRegularPolygonCenterPoint;
3768       break;
3769     case 2:
3770       defActionRegularPolygon = mActionRegularPolygonCenterCorner;
3771       break;
3772   }
3773   tbAddRegularPolygon->setDefaultAction( defActionRegularPolygon );
3774   QAction *addRegularPolygonAction = mShapeDigitizeToolBar->insertWidget( mActionVertexTool, tbAddRegularPolygon );
3775   addRegularPolygonAction->setObjectName( QStringLiteral( "ActionAddRegularPolygon" ) );
3776   connect( tbAddRegularPolygon, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3777 
3778   // Cad toolbar
3779   mAdvancedDigitizeToolBar->insertAction( mAdvancedDigitizeToolBar->actions().at( 0 ), mAdvancedDigitizingDockWidget->enableAction() );
3780 
3781   // move feature tool button
3782   QToolButton *moveFeatureButton = new QToolButton( mAdvancedDigitizeToolBar );
3783   moveFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
3784   moveFeatureButton->addAction( mActionMoveFeature );
3785   moveFeatureButton->addAction( mActionMoveFeatureCopy );
3786   QAction *defAction = mActionMoveFeature;
3787   switch ( settings.value( QStringLiteral( "UI/defaultMoveTool" ), 0 ).toInt() )
3788   {
3789     case 0:
3790       defAction = mActionMoveFeature;
3791       break;
3792     case 1:
3793       defAction = mActionMoveFeatureCopy;
3794       break;
3795   }
3796   moveFeatureButton->setDefaultAction( defAction );
3797   QAction *moveToolAction = mAdvancedDigitizeToolBar->insertWidget( mActionRotateFeature, moveFeatureButton );
3798   moveToolAction->setObjectName( QStringLiteral( "ActionMoveFeatureTool" ) );
3799   connect( moveFeatureButton, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3800 
3801   // vertex tool button
3802   QToolButton *vertexToolButton = new QToolButton( mDigitizeToolBar );
3803   vertexToolButton->setPopupMode( QToolButton::MenuButtonPopup );
3804   vertexToolButton->addAction( mActionVertexTool );
3805   vertexToolButton->addAction( mActionVertexToolActiveLayer );
3806   QAction *defActionVertexTool = mActionVertexTool;
3807   switch ( settings.enumValue( QStringLiteral( "UI/defaultVertexTool" ), QgsVertexTool::ActiveLayer ) )
3808   {
3809     case QgsVertexTool::AllLayers:
3810       defActionVertexTool = mActionVertexTool;
3811       break;
3812     case QgsVertexTool::ActiveLayer:
3813       defActionVertexTool = mActionVertexToolActiveLayer;
3814       break;
3815   }
3816   vertexToolButton->setDefaultAction( defActionVertexTool );
3817   QAction *actionVertexTool = mDigitizeToolBar->insertWidget( mActionMultiEditAttributes, vertexToolButton );
3818   actionVertexTool->setObjectName( QStringLiteral( "ActionVertexTool" ) );
3819   connect( vertexToolButton, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3820 
3821   bt = new QToolButton();
3822   bt->setPopupMode( QToolButton::MenuButtonPopup );
3823   bt->addAction( mActionRotatePointSymbols );
3824   bt->addAction( mActionOffsetPointSymbol );
3825 
3826   QAction *defPointSymbolAction = mActionRotatePointSymbols;
3827   switch ( settings.value( QStringLiteral( "UI/defaultPointSymbolAction" ), 0 ).toInt() )
3828   {
3829     case 0:
3830       defPointSymbolAction = mActionRotatePointSymbols;
3831       break;
3832     case 1:
3833       defPointSymbolAction = mActionOffsetPointSymbol;
3834       break;
3835   }
3836   bt->setDefaultAction( defPointSymbolAction );
3837   QAction *pointSymbolAction = mAdvancedDigitizeToolBar->addWidget( bt );
3838   pointSymbolAction->setObjectName( QStringLiteral( "ActionPointSymbolTools" ) );
3839   connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3840 
3841   QgsMapToolEditMeshFrame *editMeshMapTool = qobject_cast<QgsMapToolEditMeshFrame *>( mMapTools->mapTool( QgsAppMapTools::EditMeshFrame ) );
3842   if ( editMeshMapTool )
3843   {
3844     mMeshToolBar->addAction( editMeshMapTool->digitizeAction() );
3845 
3846     QToolButton *meshSelectToolButton = new QToolButton();
3847     meshSelectToolButton->setPopupMode( QToolButton::MenuButtonPopup );
3848     QList<QAction *> selectActions = editMeshMapTool->selectActions();
3849     for ( QAction *selectAction : selectActions )
3850     {
3851       meshSelectToolButton->addAction( selectAction );
3852       connect( selectAction, &QAction::triggered, meshSelectToolButton, [selectAction, meshSelectToolButton]
3853       {
3854         meshSelectToolButton->setDefaultAction( selectAction );
3855       } );
3856     }
3857 
3858     meshSelectToolButton->setDefaultAction( editMeshMapTool->defaultSelectActions() );
3859     mMeshToolBar->addWidget( meshSelectToolButton );
3860 
3861     mMeshToolBar->addAction( ( editMeshMapTool->transformAction() ) );
3862 
3863     QToolButton *meshForceByLinesToolButton = new QToolButton();
3864     meshForceByLinesToolButton->setPopupMode( QToolButton::MenuButtonPopup );
3865     QMenu *meshForceByLineMenu = new QMenu( meshForceByLinesToolButton );
3866 
3867     //meshForceByLineMenu->addActions( editMeshMapTool->forceByLinesActions() );
3868     meshForceByLinesToolButton->setDefaultAction( editMeshMapTool->defaultForceAction() );
3869     meshForceByLineMenu->addSeparator();
3870     meshForceByLineMenu->addAction( editMeshMapTool->forceByLineWidgetActionSettings() );
3871     meshForceByLinesToolButton->setMenu( meshForceByLineMenu );
3872     mMeshToolBar->addWidget( meshForceByLinesToolButton );
3873 
3874     digitizeMenu->addAction( mActionStreamDigitize );
3875     digitizeMenu->addSeparator();
3876     digitizeMenu->addAction( mMapTools->streamDigitizingSettingsAction() );
3877     mDigitizeModeToolButton->setMenu( digitizeMenu );
3878     for ( QAction *mapToolAction : editMeshMapTool->mapToolActions() )
3879       mMapToolGroup->addAction( mapToolAction );
3880 
3881     mMeshMenu->addAction( editMeshMapTool->reindexAction() );
3882   }
3883 
3884   QToolButton *annotationLayerToolButton = new QToolButton();
3885   annotationLayerToolButton->setPopupMode( QToolButton::MenuButtonPopup );
3886   QMenu *annotationLayerMenu = new QMenu( annotationLayerToolButton );
3887   annotationLayerMenu->addAction( mActionCreateAnnotationLayer );
3888   annotationLayerMenu->addAction( mMainAnnotationLayerProperties );
3889   annotationLayerToolButton->setMenu( annotationLayerMenu );
3890   annotationLayerToolButton->setDefaultAction( mActionCreateAnnotationLayer );
3891   mAnnotationsToolBar->insertWidget( mAnnotationsToolBar->actions().at( 0 ), annotationLayerToolButton );
3892 
3893   // Registered annotation items will be inserted before this separator
3894   mAnnotationsItemInsertBefore = mAnnotationsToolBar->addSeparator();
3895 
3896   bt = new QToolButton();
3897   bt->setPopupMode( QToolButton::MenuButtonPopup );
3898   bt->addAction( mActionTextAnnotation );
3899   bt->addAction( mActionFormAnnotation );
3900   bt->addAction( mActionHtmlAnnotation );
3901   bt->addAction( mActionSvgAnnotation );
3902   bt->addAction( mActionAnnotation );
3903 
3904   QAction *defAnnotationAction = mActionTextAnnotation;
3905   switch ( settings.value( QStringLiteral( "UI/annotationTool" ), 0 ).toInt() )
3906   {
3907     case 0:
3908       defAnnotationAction = mActionTextAnnotation;
3909       break;
3910     case 1:
3911       defAnnotationAction = mActionFormAnnotation;
3912       break;
3913     case 2:
3914       defAnnotationAction = mActionHtmlAnnotation;
3915       break;
3916     case 3:
3917       defAnnotationAction = mActionSvgAnnotation;
3918       break;
3919     case 4:
3920       defAnnotationAction = mActionAnnotation;
3921       break;
3922   }
3923   bt->setDefaultAction( defAnnotationAction );
3924   QAction *annotationAction = mAnnotationsToolBar->addWidget( bt );
3925   annotationAction->setObjectName( QStringLiteral( "ActionAnnotation" ) );
3926   connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
3927 }
3928 
createStatusBar()3929 void QgisApp::createStatusBar()
3930 {
3931   //remove borders from children under Windows
3932   statusBar()->setStyleSheet( QStringLiteral( "QStatusBar::item {border: none;}" ) );
3933 
3934   // Drop the font size in the status bar by a couple of points
3935   QFont statusBarFont = font();
3936   int fontSize = statusBarFont.pointSize();
3937 #ifdef Q_OS_WIN
3938   fontSize = std::max( fontSize - 1, 8 ); // bit less on windows, due to poor rendering of small point sizes
3939 #else
3940   fontSize = std::max( fontSize - 2, 6 );
3941 #endif
3942   statusBarFont.setPointSize( fontSize );
3943   statusBar()->setFont( statusBarFont );
3944 
3945   mStatusBar = new QgsStatusBar();
3946   mStatusBar->setParentStatusBar( QMainWindow::statusBar() );
3947   mStatusBar->setFont( statusBarFont );
3948 
3949   statusBar()->addPermanentWidget( mStatusBar, 10 );
3950 
3951   // Add a panel to the status bar for the scale, coords and progress
3952   // And also rendering suppression checkbox
3953   mProgressBar = new QProgressBar( mStatusBar );
3954   mProgressBar->setObjectName( QStringLiteral( "mProgressBar" ) );
3955   mProgressBar->setMaximumWidth( 100 );
3956   mProgressBar->setMaximumHeight( 18 );
3957   mProgressBar->hide();
3958   mStatusBar->addPermanentWidget( mProgressBar, 1 );
3959 
3960   connect( mMapCanvas, &QgsMapCanvas::renderStarting, this, &QgisApp::canvasRefreshStarted );
3961   connect( mMapCanvas, &QgsMapCanvas::mapCanvasRefreshed, this, &QgisApp::canvasRefreshFinished );
3962 
3963   mTaskManagerWidget = new QgsTaskManagerStatusBarWidget( QgsApplication::taskManager(), mStatusBar );
3964   mTaskManagerWidget->setFont( statusBarFont );
3965   mStatusBar->addPermanentWidget( mTaskManagerWidget, 0 );
3966 
3967   //coords status bar widget
3968   mCoordsEdit = new QgsStatusBarCoordinatesWidget( mStatusBar );
3969   mCoordsEdit->setObjectName( QStringLiteral( "mCoordsEdit" ) );
3970   mCoordsEdit->setMapCanvas( mMapCanvas );
3971   mCoordsEdit->setFont( statusBarFont );
3972   mStatusBar->addPermanentWidget( mCoordsEdit, 0 );
3973 
3974   mScaleWidget = new QgsStatusBarScaleWidget( mMapCanvas, mStatusBar );
3975   mScaleWidget->setObjectName( QStringLiteral( "mScaleWidget" ) );
3976   mScaleWidget->setFont( statusBarFont );
3977   mStatusBar->addPermanentWidget( mScaleWidget, 0 );
3978 
3979   // zoom widget
3980   mMagnifierWidget = new QgsStatusBarMagnifierWidget( mStatusBar );
3981   mMagnifierWidget->setObjectName( QStringLiteral( "mMagnifierWidget" ) );
3982   mMagnifierWidget->setFont( statusBarFont );
3983   connect( mMapCanvas, &QgsMapCanvas::magnificationChanged, mMagnifierWidget, &QgsStatusBarMagnifierWidget::updateMagnification );
3984   connect( mMapCanvas, &QgsMapCanvas::scaleLockChanged, mMagnifierWidget, &QgsStatusBarMagnifierWidget::updateScaleLock );
3985   connect( mMagnifierWidget, &QgsStatusBarMagnifierWidget::magnificationChanged, mMapCanvas, [ = ]( double factor ) { mMapCanvas->setMagnificationFactor( factor ); } );
3986   connect( mMagnifierWidget, &QgsStatusBarMagnifierWidget::scaleLockChanged, mMapCanvas, &QgsMapCanvas::setScaleLocked );
3987   mMagnifierWidget->updateMagnification( QSettings().value( QStringLiteral( "/qgis/magnifier_factor_default" ), 1.0 ).toDouble() );
3988   mStatusBar->addPermanentWidget( mMagnifierWidget, 0 );
3989 
3990   // add a widget to show/set current rotation
3991   mRotationLabel = new QLabel( QString(), mStatusBar );
3992   mRotationLabel->setObjectName( QStringLiteral( "mRotationLabel" ) );
3993   mRotationLabel->setFont( statusBarFont );
3994   mRotationLabel->setMinimumWidth( 10 );
3995   //mRotationLabel->setMaximumHeight( 20 );
3996   mRotationLabel->setMargin( 3 );
3997   mRotationLabel->setAlignment( Qt::AlignCenter );
3998   mRotationLabel->setFrameStyle( QFrame::NoFrame );
3999   mRotationLabel->setText( tr( "Rotation" ) );
4000   mRotationLabel->setToolTip( tr( "Current clockwise map rotation in degrees" ) );
4001   mStatusBar->addPermanentWidget( mRotationLabel, 0 );
4002 
4003   mRotationEdit = new QgsDoubleSpinBox( mStatusBar );
4004   mRotationEdit->setObjectName( QStringLiteral( "mRotationEdit" ) );
4005   mRotationEdit->setClearValue( 0.0 );
4006   mRotationEdit->setKeyboardTracking( false );
4007   mRotationEdit->setMaximumWidth( 120 );
4008   mRotationEdit->setDecimals( 1 );
4009   mRotationEdit->setRange( -360.0, 360.0 );
4010   mRotationEdit->setWrapping( true );
4011   mRotationEdit->setSingleStep( 5.0 );
4012   mRotationEdit->setFont( statusBarFont );
4013   mRotationEdit->setSuffix( tr( " °" ) );
4014   mRotationEdit->setToolTip( tr( "Current clockwise map rotation in degrees" ) );
4015   mStatusBar->addPermanentWidget( mRotationEdit, 0 );
4016   connect( mRotationEdit, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgisApp::userRotation );
4017 
4018   showRotation();
4019 
4020   // render suppression status bar widget
4021   mRenderSuppressionCBox = new QCheckBox( tr( "Render" ), mStatusBar );
4022   mRenderSuppressionCBox->setObjectName( QStringLiteral( "mRenderSuppressionCBox" ) );
4023   mRenderSuppressionCBox->setChecked( true );
4024   mRenderSuppressionCBox->setFont( statusBarFont );
4025   mRenderSuppressionCBox->setToolTip( tr( "Toggle map rendering" ) );
4026   mStatusBar->addPermanentWidget( mRenderSuppressionCBox, 0 );
4027   // On the fly projection status bar icon
4028   // Changed this to a tool button since a QPushButton is
4029   // sculpted on OS X and the icon is never displayed [gsherman]
4030   mOnTheFlyProjectionStatusButton = new QToolButton( mStatusBar );
4031   mOnTheFlyProjectionStatusButton->setAutoRaise( true );
4032   mOnTheFlyProjectionStatusButton->setFont( statusBarFont );
4033   mOnTheFlyProjectionStatusButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
4034   mOnTheFlyProjectionStatusButton->setObjectName( QStringLiteral( "mOntheFlyProjectionStatusButton" ) );
4035   // Maintain uniform widget height in status bar by setting button height same as labels
4036   // For Qt/Mac 3.3, the default toolbutton height is 30 and labels were expanding to match
4037   mOnTheFlyProjectionStatusButton->setMaximumHeight( mScaleWidget->height() );
4038   mOnTheFlyProjectionStatusButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconProjectionEnabled.svg" ) ) );
4039   mOnTheFlyProjectionStatusButton->setToolTip( tr( "CRS status - Click "
4040       "to open coordinate reference system dialog" ) );
4041   connect( mOnTheFlyProjectionStatusButton, &QAbstractButton::clicked,
4042            this, &QgisApp::projectPropertiesProjections );//bring up the project props dialog when clicked
4043   mStatusBar->addPermanentWidget( mOnTheFlyProjectionStatusButton, 0 );
4044   mStatusBar->showMessage( tr( "Ready" ) );
4045 
4046   mMessageButton = new QToolButton( mStatusBar );
4047   mMessageButton->setAutoRaise( true );
4048   mMessageButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mMessageLogRead.svg" ) ) );
4049   mMessageButton->setToolTip( tr( "Messages" ) );
4050   mMessageButton->setObjectName( QStringLiteral( "mMessageLogViewerButton" ) );
4051   mMessageButton->setMaximumHeight( mScaleWidget->height() );
4052   mMessageButton->setCheckable( true );
4053   mStatusBar->addPermanentWidget( mMessageButton, 0 );
4054 
4055   mLocatorWidget = new QgsLocatorWidget( mStatusBar );
4056   mStatusBar->addPermanentWidget( mLocatorWidget, 0, QgsStatusBar::AnchorLeft );
4057   QShortcut *locatorShortCut = new QShortcut( QKeySequence( tr( "Ctrl+K" ) ), this );
4058   connect( locatorShortCut, &QShortcut::activated, mLocatorWidget, [ = ] { mLocatorWidget->search( QString() ); } );
4059   locatorShortCut->setObjectName( QStringLiteral( "Locator" ) );
4060   locatorShortCut->setWhatsThis( tr( "Trigger Locator" ) );
4061 
4062   mLocatorWidget->locator()->registerFilter( new QgsLayerTreeLocatorFilter() );
4063   mLocatorWidget->locator()->registerFilter( new QgsLayoutLocatorFilter() );
4064   QList< QWidget *> actionObjects;
4065   actionObjects << menuBar()
4066                 << mAdvancedDigitizeToolBar
4067                 << mShapeDigitizeToolBar
4068                 << mFileToolBar
4069                 << mDataSourceManagerToolBar
4070                 << mLayerToolBar
4071                 << mDigitizeToolBar
4072                 << mMapNavToolBar
4073                 << mAttributesToolBar
4074                 << mPluginToolBar
4075                 << mRasterToolBar
4076                 << mLabelToolBar
4077                 << mVectorToolBar
4078                 << mDatabaseToolBar
4079                 << mWebToolBar
4080                 << mSnappingToolBar;
4081 
4082   mLocatorWidget->locator()->registerFilter( new QgsActionLocatorFilter( actionObjects ) );
4083   mLocatorWidget->locator()->registerFilter( new QgsActiveLayerFeaturesLocatorFilter() );
4084   mLocatorWidget->locator()->registerFilter( new QgsAllLayersFeaturesLocatorFilter() );
4085   mLocatorWidget->locator()->registerFilter( new QgsExpressionCalculatorLocatorFilter() );
4086   mLocatorWidget->locator()->registerFilter( new QgsBookmarkLocatorFilter() );
4087   mLocatorWidget->locator()->registerFilter( new QgsSettingsLocatorFilter() );
4088   mLocatorWidget->locator()->registerFilter( new QgsGotoLocatorFilter() );
4089 
4090   mNominatimGeocoder = std::make_unique< QgsNominatimGeocoder>();
4091   mLocatorWidget->locator()->registerFilter( new QgsNominatimLocatorFilter( mNominatimGeocoder.get(), mMapCanvas ) );
4092 }
4093 
setIconSizes(int size)4094 void QgisApp::setIconSizes( int size )
4095 {
4096   QSize iconSize = QSize( size, size );
4097   QSize panelIconSize = QgsGuiUtils::panelIconSize( iconSize );
4098 
4099   //Set the icon size of for all the toolbars created in the future.
4100   setIconSize( iconSize );
4101 
4102   //Change all current icon sizes.
4103   QList<QToolBar *> toolbars = findChildren<QToolBar *>();
4104   const auto constToolbars = toolbars;
4105   for ( QToolBar *toolbar : constToolbars )
4106   {
4107     QString className = toolbar->parent()->metaObject()->className();
4108     if ( className == QLatin1String( "QgisApp" ) )
4109     {
4110       toolbar->setIconSize( iconSize );
4111     }
4112     else
4113     {
4114       toolbar->setIconSize( panelIconSize );
4115     }
4116   }
4117 
4118   const auto constMLayoutDesignerDialogs = mLayoutDesignerDialogs;
4119   for ( QgsLayoutDesignerDialog *d : constMLayoutDesignerDialogs )
4120   {
4121     d->setIconSizes( size );
4122   }
4123 }
4124 
setTheme(const QString & themeName)4125 void QgisApp::setTheme( const QString &themeName )
4126 {
4127   /*
4128   Init the toolbar icons by setting the icon for each action.
4129   All toolbar/menu items must be a QAction in order for this
4130   to work.
4131 
4132   When new toolbar/menu QAction objects are added to the interface,
4133   add an entry below to set the icon
4134 
4135   PNG names must match those defined for the default theme. The
4136   default theme is installed in <prefix>/share/qgis/themes/default.
4137 
4138   New core themes can be added by creating a subdirectory under src/themes
4139   and modifying the appropriate CMakeLists.txt files. User contributed themes
4140   will be installed directly into <prefix>/share/qgis/themes/<themedir>.
4141 
4142   Themes can be selected from the preferences dialog. The dialog parses
4143   the themes directory and builds a list of themes (ie subdirectories)
4144   for the user to choose from.
4145   */
4146 
4147   QString theme = themeName;
4148 
4149   mStyleSheetBuilder->buildStyleSheet( mStyleSheetBuilder->defaultOptions() );
4150   QgsApplication::setUITheme( theme );
4151 
4152   mActionNewProject->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileNew.svg" ) ) );
4153   mActionOpenProject->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileOpen.svg" ) ) );
4154   mActionSaveProject->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileSave.svg" ) ) );
4155   mActionSaveProjectAs->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileSaveAs.svg" ) ) );
4156   mActionSaveMapAsImage->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveMapAsImage.svg" ) ) );
4157   mActionSaveMapAsPdf->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAsPDF.svg" ) ) );
4158   mActionExit->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileExit.png" ) ) );
4159   mActionAddOgrLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddOgrLayer.svg" ) ) );
4160   mActionAddRasterLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddRasterLayer.svg" ) ) );
4161 #ifdef HAVE_POSTGRESQL
4162   mActionAddPgLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPostgisLayer.svg" ) ) );
4163 #endif
4164 #ifdef HAVE_SPATIALITE
4165   mActionNewSpatiaLiteLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewSpatiaLiteLayer.svg" ) ) );
4166   mActionAddSpatiaLiteLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddSpatiaLiteLayer.svg" ) ) );
4167 #endif
4168   mActionAddMssqlLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddMssqlLayer.svg" ) ) );
4169 #ifdef HAVE_ORACLE
4170   mActionAddOracleLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddOracleLayer.svg" ) ) );
4171 #endif
4172 #ifdef HAVE_HANA
4173   mActionAddHanaLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddHanaLayer.svg" ) ) );
4174 #endif
4175   mActionRemoveLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveLayer.svg" ) ) );
4176   mActionDuplicateLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateLayer.svg" ) ) );
4177   mActionSetLayerCRS->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSetLayerCRS.png" ) ) );
4178   mActionSetProjectCRSFromLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSetProjectCRSFromLayer.png" ) ) );
4179   mActionNewVectorLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewVectorLayer.svg" ) ) );
4180   mActionDataSourceManager->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDataSourceManager.svg" ) ) );
4181   mActionNewMemoryLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCreateMemory.svg" ) ) );
4182   mActionAddAllToOverview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddAllToOverview.svg" ) ) );
4183   mActionHideAllLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHideAllLayers.svg" ) ) );
4184   mActionShowAllLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowAllLayers.svg" ) ) );
4185   mActionHideSelectedLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHideSelectedLayers.svg" ) ) );
4186   mActionHideDeselectedLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHideDeselectedLayers.svg" ) ) );
4187   mActionShowSelectedLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowSelectedLayers.svg" ) ) );
4188   mActionRemoveAllFromOverview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveAllFromOverview.svg" ) ) );
4189   mActionToggleFullScreen->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleFullScreen.png" ) ) );
4190   mActionProjectProperties->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionProjectProperties.svg" ) ) );
4191   mActionManagePlugins->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowPluginManager.svg" ) ) );
4192   mActionShowPythonDialog->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/mIconRunConsole.svg" ) ) );
4193   mActionCheckQgisVersion->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSuccess.svg" ) ) );
4194   mActionOptions->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOptions.svg" ) ) );
4195   mActionConfigureShortcuts->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionKeyboardShortcuts.svg" ) ) );
4196   mActionCustomization->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInterfaceCustomization.svg" ) ) );
4197   mActionHelpContents->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHelpContents.svg" ) ) );
4198   mActionLocalHistogramStretch->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLocalHistogramStretch.svg" ) ) );
4199   mActionFullHistogramStretch->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFullHistogramStretch.svg" ) ) );
4200   mActionIncreaseBrightness->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIncreaseBrightness.svg" ) ) );
4201   mActionDecreaseBrightness->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDecreaseBrightness.svg" ) ) );
4202   mActionIncreaseContrast->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIncreaseContrast.svg" ) ) );
4203   mActionDecreaseContrast->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDecreaseContrast.svg" ) ) );
4204   mActionIncreaseGamma->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIncreaseGamma.svg" ) ) );
4205   mActionDecreaseGamma->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDecreaseGamma.svg" ) ) );
4206   mActionZoomActualSize->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomNative.png" ) ) );
4207   mActionQgisHomePage->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionQgisHomePage.png" ) ) );
4208   mActionAbout->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHelpAbout.svg" ) ) );
4209   mActionSponsors->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHelpSponsors.png" ) ) );
4210   mActionDraw->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRefresh.svg" ) ) );
4211   mActionToggleEditing->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
4212   mActionSaveLayerEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAllEdits.svg" ) ) );
4213   mActionAllEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAllEdits.svg" ) ) );
4214   mActionSaveEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) );
4215   mActionSaveAllEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAllEdits.svg" ) ) );
4216   mActionRollbackEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRollbackEdits.svg" ) ) );
4217   mActionRollbackAllEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRollbackAllEdits.svg" ) ) );
4218   mActionCancelEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCancelEdits.svg" ) ) );
4219   mActionCancelAllEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCancelAllEdits.svg" ) ) );
4220   mActionCutFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCut.svg" ) ) );
4221   mActionCopyFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ) );
4222   mActionPasteFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditPaste.svg" ) ) );
4223   mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) ) );
4224   mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeaturePoint.svg" ) ) );
4225   mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyPoint.svg" ) ) );
4226   mActionRotateFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRotateFeature.svg" ) ) );
4227   mActionScaleFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleFeature.svg" ) ) );
4228   mActionReshapeFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionReshape.svg" ) ) );
4229   mActionSplitFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSplitFeatures.svg" ) ) );
4230   mActionSplitParts->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSplitParts.svg" ) ) );
4231   mActionDeleteSelected->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelectedFeatures.svg" ) ) );
4232   mActionVertexTool->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionVertexTool.svg" ) ) );
4233   mActionVertexToolActiveLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionVertexToolActiveLayer.svg" ) ) );
4234   mActionSimplifyFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSimplify.svg" ) ) );
4235   mActionUndo->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUndo.svg" ) ) );
4236   mActionRedo->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRedo.svg" ) ) );
4237   mActionAddRing->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddRing.svg" ) ) );
4238   mActionFillRing->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFillRing.svg" ) ) );
4239   mActionAddPart->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPart.svg" ) ) );
4240   mActionDeleteRing->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteRing.svg" ) ) );
4241   mActionDeletePart->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeletePart.svg" ) ) );
4242   mActionMergeFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMergeFeatures.svg" ) ) );
4243   mActionOffsetCurve->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOffsetCurve.svg" ) ) );
4244   mActionMergeFeatureAttributes->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMergeFeatureAttributes.svg" ) ) );
4245   mActionRotatePointSymbols->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionRotatePointSymbols.svg" ) ) );
4246   mActionOffsetPointSymbol->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionOffsetPointSymbols.svg" ) ) );
4247   mActionZoomIn->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomIn.svg" ) ) );
4248   mActionZoomOut->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomOut.svg" ) ) );
4249   mActionZoomFullExtent->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomFullExtent.svg" ) ) );
4250   mActionZoomToSelected->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToSelected.svg" ) ) );
4251   mActionShowRasterCalculator->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowRasterCalculator.png" ) ) );
4252   mActionShowMeshCalculator->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowMeshCalculator.png" ) ) );
4253   mActionPan->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPan.svg" ) ) );
4254   mActionPanToSelected->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanToSelected.svg" ) ) );
4255   mActionZoomLast->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomLast.svg" ) ) );
4256   mActionZoomNext->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomNext.svg" ) ) );
4257   mActionZoomToLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToLayer.svg" ) ) );
4258   mActionZoomToLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToLayer.svg" ) ) );
4259   mActionZoomActualSize->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomActual.svg" ) ) );
4260   mActionIdentify->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIdentify.svg" ) ) );
4261   mActionFeatureAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mAction.svg" ) ) );
4262   mActionSelectFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectRectangle.svg" ) ) );
4263   mActionSelectPolygon->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectPolygon.svg" ) ) );
4264   mActionSelectFreehand->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectFreehand.svg" ) ) );
4265   mActionSelectRadius->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectRadius.svg" ) ) );
4266   mActionDeselectAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeselectAll.svg" ) ) );
4267   mActionDeselectActiveLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeselectActiveLayer.svg" ) ) );
4268   mActionSelectAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectAll.svg" ) ) );
4269   mActionInvertSelection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInvertSelection.svg" ) ) );
4270   mActionSelectByExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionSelect.svg" ) ) );
4271   mActionSelectByForm->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
4272   mActionOpenTable->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) ) );
4273   mActionOpenTableSelected->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTableSelected.svg" ) ) );
4274   mActionOpenTableVisible->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTableVisible.svg" ) ) );
4275   mActionOpenTableEdited->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTableEdited.svg" ) ) );
4276   mActionOpenFieldCalc->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCalculateField.svg" ) ) );
4277   mActionMeasure->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMeasure.svg" ) ) );
4278   mActionMeasureArea->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMeasureArea.svg" ) ) );
4279   mActionMeasureAngle->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMeasureAngle.svg" ) ) );
4280   mActionMeasureBearing->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMeasureBearing.svg" ) ) );
4281   mActionMapTips->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapTips.svg" ) ) );
4282   mActionShowBookmarkManager->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowBookmarks.svg" ) ) );
4283   mActionShowBookmarks->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowBookmarks.svg" ) ) );
4284   mActionNewBookmark->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewBookmark.svg" ) ) );
4285   mActionCustomProjection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCustomProjection.svg" ) ) );
4286   mActionAddWmsLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddWmsLayer.svg" ) ) );
4287   mActionAddXyzLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddXyzLayer.svg" ) ) );
4288   mActionAddVectorTileLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddVectorTileLayer.svg" ) ) );
4289   mActionAddWcsLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddWcsLayer.svg" ) ) );
4290 #ifdef HAVE_SPATIALITE
4291   mActionAddWfsLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddWfsLayer.svg" ) ) );
4292 #endif
4293   mActionAddAfsLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddAfsLayer.svg" ) ) );
4294   mActionAddToOverview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInOverview.svg" ) ) );
4295   mActionAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAnnotation.svg" ) ) );
4296   mActionFormAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFormAnnotation.svg" ) ) );
4297   mActionHtmlAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHtmlAnnotation.svg" ) ) );
4298   mActionSvgAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSvgAnnotation.svg" ) ) );
4299   mActionTextAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionTextAnnotation.svg" ) ) );
4300   mActionLabeling->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLabeling.svg" ) ) );
4301   mActionShowPinnedLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowPinnedLabels.svg" ) ) );
4302   mActionPinLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPinLabels.svg" ) ) );
4303   mActionShowHideLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowHideLabels.svg" ) ) );
4304   mActionShowUnplacedLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowUnplacedLabel.svg" ) ) );
4305   mActionMoveLabel->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveLabel.svg" ) ) );
4306   mActionRotateLabel->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRotateLabel.svg" ) ) );
4307   mActionChangeLabelProperties->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionChangeLabelProperties.svg" ) ) );
4308   mActionDiagramProperties->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/diagram.svg" ) ) );
4309   mActionDecorationTitle->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/title_label.svg" ) ) );
4310   mActionDecorationCopyright->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/copyright_label.svg" ) ) );
4311   mActionDecorationImage->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddImage.svg" ) ) );
4312   mActionDecorationNorthArrow->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/north_arrow.svg" ) ) );
4313   mActionDecorationScaleBar->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleBar.svg" ) ) );
4314   mActionDecorationGrid->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/grid.svg" ) ) );
4315   mActionReverseLine->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionReverseLine.svg" ) ) );
4316   mActionTrimExtendFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionTrimExtendFeature.svg" ) ) );
4317   mActionTemporalController->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/temporal.svg" ) ) );
4318 
4319   emit currentThemeChanged( themeName );
4320 }
4321 
setupConnections()4322 void QgisApp::setupConnections()
4323 {
4324   // connect the "cleanup" slot
4325   connect( qApp, &QApplication::aboutToQuit, this, &QgisApp::saveWindowState );
4326 
4327   // signal when mouse moved over window (coords display in status bar)
4328   connect( mMapCanvas, &QgsMapCanvas::xyCoordinates, this, &QgisApp::saveLastMousePosition );
4329   connect( mMapCanvas, &QgsMapCanvas::extentsChanged, this, &QgisApp::extentChanged );
4330   connect( mMapCanvas, &QgsMapCanvas::scaleChanged, this, &QgisApp::showScale );
4331   connect( mMapCanvas, &QgsMapCanvas::rotationChanged, this, &QgisApp::showRotation );
4332   connect( mMapCanvas, &QgsMapCanvas::scaleChanged, this, &QgisApp::updateMouseCoordinatePrecision );
4333   connect( mMapCanvas, &QgsMapCanvas::mapToolSet, this, &QgisApp::mapToolChanged );
4334   connect( mMapCanvas, &QgsMapCanvas::selectionChanged, this, &QgisApp::selectionChanged );
4335   connect( mMapCanvas, &QgsMapCanvas::layersChanged, this, &QgisApp::markDirty );
4336 
4337   connect( mMapCanvas, &QgsMapCanvas::zoomLastStatusChanged, mActionZoomLast, &QAction::setEnabled );
4338   connect( mMapCanvas, &QgsMapCanvas::zoomNextStatusChanged, mActionZoomNext, &QAction::setEnabled );
4339 
4340   connect( mRenderSuppressionCBox, &QAbstractButton::toggled, this, [ = ]( bool flag )
4341   {
4342     const auto canvases = mapCanvases();
4343     for ( QgsMapCanvas *canvas : canvases )
4344       canvas->setRenderFlag( flag );
4345     if ( !flag )
4346       canvasRefreshFinished(); // deals with the busy indicator in case of ongoing rendering
4347   } );
4348 
4349   connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgisApp::reprojectAnnotations );
4350 
4351   // connect MapCanvas keyPress event so we can check if selected feature collection must be deleted
4352   connect( mMapCanvas, &QgsMapCanvas::keyPressed, this, &QgisApp::mapCanvas_keyPressed );
4353 
4354   // project crs connections
4355   connect( QgsProject::instance(), &QgsProject::crsChanged, this, &QgisApp::projectCrsChanged );
4356 
4357   connect( QgsProject::instance()->viewSettings(), &QgsProjectViewSettings::mapScalesChanged, this, [ = ] { mScaleWidget->updateScales(); } );
4358 
4359   connect( QgsProject::instance(), &QgsProject::missingDatumTransforms, this, [ = ]( const QStringList & transforms )
4360   {
4361     QString message = tr( "Transforms are not installed: %1 " ).arg( transforms.join( QLatin1String( " ," ) ) );
4362     messageBar()->pushWarning( tr( "Missing datum transforms" ), message );
4363   } );
4364 
4365   connect( QgsProject::instance(), &QgsProject::labelingEngineSettingsChanged,
4366            mMapCanvas, [ = ]
4367   {
4368     mMapCanvas->setLabelingEngineSettings( QgsProject::instance()->labelingEngineSettings() );
4369   } );
4370 
4371   connect( QgsProject::instance(), &QgsProject::backgroundColorChanged, this, [ = ]
4372   {
4373     const QColor backgroundColor = QgsProject::instance()->backgroundColor();
4374     const auto constMapCanvases = mapCanvases();
4375     for ( QgsMapCanvas *canvas : constMapCanvases )
4376     {
4377       canvas->setCanvasColor( backgroundColor );
4378     }
4379     if ( auto *lMapOverviewCanvas = mapOverviewCanvas() )
4380     {
4381       lMapOverviewCanvas->setBackgroundColor( backgroundColor );
4382       lMapOverviewCanvas->refresh();
4383     }
4384   } );
4385 
4386   connect( QgsProject::instance(), &QgsProject::selectionColorChanged, this, [ = ]
4387   {
4388     const QColor selectionColor = QgsProject::instance()->selectionColor();
4389     const auto constMapCanvases = mapCanvases();
4390     for ( QgsMapCanvas *canvas : constMapCanvases )
4391     {
4392       canvas->setSelectionColor( selectionColor );
4393     }
4394   } );
4395 
4396   connect( QgsProject::instance()->timeSettings(), &QgsProjectTimeSettings::temporalRangeChanged, this, &QgisApp::projectTemporalRangeChanged );
4397 
4398   // connect legend signals
4399   connect( this, &QgisApp::activeLayerChanged,
4400            this, &QgisApp::activateDeactivateLayerRelatedActions );
4401   connect( this, &QgisApp::activeLayerChanged,
4402            this, &QgisApp::setMapStyleDockLayer );
4403   connect( mLayerTreeView->selectionModel(), &QItemSelectionModel::selectionChanged,
4404            this, &QgisApp::legendLayerSelectionChanged );
4405   connect( mLayerTreeView->selectionModel(), &QItemSelectionModel::selectionChanged,
4406            this, &QgisApp::activateDeactivateMultipleLayersRelatedActions );
4407   connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::addedChildren,
4408            this, &QgisApp::markDirty );
4409   connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::addedChildren,
4410            this, &QgisApp::updateNewLayerInsertionPoint );
4411   connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::removedChildren,
4412            this, &QgisApp::markDirty );
4413   connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::removedChildren,
4414            this, &QgisApp::updateNewLayerInsertionPoint );
4415   connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::visibilityChanged,
4416            this, &QgisApp::markDirty );
4417   connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::customPropertyChanged,
4418            this, [ = ]( QgsLayerTreeNode *, const QString & key )
4419   {
4420     // only mark dirty for non-view only changes
4421     if ( !QgsLayerTreeView::viewOnlyCustomProperties().contains( key ) )
4422       QgisApp::markDirty();
4423   } );
4424 
4425   // connect map layer registry
4426   connect( QgsProject::instance(), &QgsProject::layersAdded,
4427            this, &QgisApp::layersWereAdded );
4428   connect( QgsProject::instance(),
4429            static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ),
4430            this, &QgisApp::removingLayers );
4431 
4432   // connect initialization signal
4433   connect( this, &QgisApp::initializationCompleted,
4434            this, &QgisApp::fileOpenAfterLaunch );
4435 
4436   // Connect warning dialog from project reading
4437   connect( QgsProject::instance(), &QgsProject::oldProjectVersionWarning,
4438            this, &QgisApp::oldProjectVersionWarning );
4439   connect( QgsProject::instance(), &QgsProject::layerLoaded,
4440            this, [this]( int i, int n )
4441   {
4442     if ( !mProjectLoadingProxyTask && i < n )
4443     {
4444       const QString name = QgsProject::instance()->title().isEmpty() ? QgsProject::instance()->fileName() : QgsProject::instance()->title();
4445       mProjectLoadingProxyTask = new QgsProxyProgressTask( tr( "Loading “%1”" ).arg( name ) );
4446       QgsApplication::taskManager()->addTask( mProjectLoadingProxyTask );
4447     }
4448 
4449     if ( mProjectLoadingProxyTask )
4450     {
4451       mProjectLoadingProxyTask->setProxyProgress( 100.0 * static_cast< double >( i ) / n );
4452       if ( i >= n )
4453       {
4454         mProjectLoadingProxyTask->finalize( true );
4455         mProjectLoadingProxyTask = nullptr;
4456       }
4457     }
4458   } );
4459   connect( QgsProject::instance(), &QgsProject::loadingLayer,
4460            this, &QgisApp::showStatusMessage );
4461   connect( QgsProject::instance(), &QgsProject::loadingLayerMessageReceived,
4462            this, &QgisApp::loadingLayerMessages );
4463   connect( QgsProject::instance(), &QgsProject::readProject,
4464            this, &QgisApp::readProject );
4465   connect( QgsProject::instance(), &QgsProject::writeProject,
4466            this, &QgisApp::writeProject );
4467 
4468   connect( this, &QgisApp::projectRead,
4469            this, &QgisApp::fileOpenedOKAfterLaunch );
4470 
4471   connect( QgsProject::instance(), &QgsProject::transactionGroupsChanged, this, &QgisApp::onTransactionGroupsChanged );
4472 
4473   // connect preview modes actions
4474   connect( mActionPreviewModeOff, &QAction::triggered, this, &QgisApp::disablePreviewMode );
4475   connect( mActionPreviewModeMono, &QAction::triggered, this, &QgisApp::activateMonoPreview );
4476   connect( mActionPreviewModeGrayscale, &QAction::triggered, this, &QgisApp::activateGrayscalePreview );
4477   connect( mActionPreviewProtanope, &QAction::triggered, this, &QgisApp::activateProtanopePreview );
4478   connect( mActionPreviewDeuteranope, &QAction::triggered, this, &QgisApp::activateDeuteranopePreview );
4479   connect( mActionPreviewTritanope, &QAction::triggered, this, &QgisApp::activateTritanopePreview );
4480 
4481   // setup undo/redo actions
4482   connect( mUndoWidget, &QgsUndoWidget::undoStackChanged, this, &QgisApp::updateUndoActions );
4483 
4484   connect( mLayoutsMenu, &QMenu::aboutToShow, this, &QgisApp::layoutsMenuAboutToShow );
4485 }
4486 
setupCanvasTools()4487 void QgisApp::setupCanvasTools()
4488 {
4489   mMapTools->mapTool( QgsAppMapTools::ZoomIn )->setAction( mActionZoomIn );
4490   mMapTools->mapTool( QgsAppMapTools::ZoomOut )->setAction( mActionZoomOut );
4491   connect( mMapTools->mapTool< QgsMapToolPan >( QgsAppMapTools::Pan ), &QgsMapToolPan::panDistanceBearingChanged, this, &QgisApp::showPanMessage );
4492   mMapTools->mapTool( QgsAppMapTools::Pan )->setAction( mActionPan );
4493   mMapTools->mapTool( QgsAppMapTools::Identify )->setAction( mActionIdentify );
4494   connect( mMapTools->mapTool< QgsMapToolIdentifyAction >( QgsAppMapTools::Identify ), &QgsMapToolIdentifyAction::copyToClipboard,
4495            this, &QgisApp::copyFeatures );
4496   mMapTools->mapTool( QgsAppMapTools::FeatureAction )->setAction( mActionFeatureAction );
4497   mMapTools->mapTool( QgsAppMapTools::MeasureDistance )->setAction( mActionMeasure );
4498   mMapTools->mapTool( QgsAppMapTools::MeasureArea )->setAction( mActionMeasureArea );
4499   mMapTools->mapTool( QgsAppMapTools::MeasureAngle )->setAction( mActionMeasureAngle );
4500   mMapTools->mapTool( QgsAppMapTools::MeasureBearing )->setAction( mActionMeasureBearing );
4501   mMapTools->mapTool( QgsAppMapTools::TextAnnotation )->setAction( mActionTextAnnotation );
4502   mMapTools->mapTool( QgsAppMapTools::FormAnnotation )->setAction( mActionFormAnnotation );
4503   mMapTools->mapTool( QgsAppMapTools::HtmlAnnotation )->setAction( mActionHtmlAnnotation );
4504   mMapTools->mapTool( QgsAppMapTools::SvgAnnotation )->setAction( mActionSvgAnnotation );
4505   mMapTools->mapTool( QgsAppMapTools::Annotation )->setAction( mActionAnnotation );
4506   mMapTools->mapTool( QgsAppMapTools::AddFeature )->setAction( mActionAddFeature );
4507   mMapTools->mapTool( QgsAppMapTools::CircularStringCurvePoint )->setAction( mActionCircularStringCurvePoint );
4508   mMapTools->mapTool( QgsAppMapTools::CircularStringRadius )->setAction( mActionCircularStringRadius );
4509   mMapTools->mapTool( QgsAppMapTools::Circle2Points )->setAction( mActionCircle2Points );
4510   mMapTools->mapTool( QgsAppMapTools::Circle3Points )->setAction( mActionCircle3Points );
4511   mMapTools->mapTool( QgsAppMapTools::Circle3Tangents )->setAction( mActionCircle3Tangents );
4512   mMapTools->mapTool( QgsAppMapTools::Circle2TangentsPoint )->setAction( mActionCircle2TangentsPoint );
4513   mMapTools->mapTool( QgsAppMapTools::CircleCenterPoint )->setAction( mActionCircleCenterPoint );
4514   mMapTools->mapTool( QgsAppMapTools::EllipseCenter2Points )->setAction( mActionEllipseCenter2Points );
4515   mMapTools->mapTool( QgsAppMapTools::EllipseCenterPoint )->setAction( mActionEllipseCenterPoint );
4516   mMapTools->mapTool( QgsAppMapTools::EllipseExtent )->setAction( mActionEllipseExtent );
4517   mMapTools->mapTool( QgsAppMapTools::EllipseFoci )->setAction( mActionEllipseFoci );
4518   mMapTools->mapTool( QgsAppMapTools::RectangleCenterPoint )->setAction( mActionRectangleCenterPoint );
4519   mMapTools->mapTool( QgsAppMapTools::RectangleExtent )->setAction( mActionRectangleExtent );
4520   mMapTools->mapTool( QgsAppMapTools::Rectangle3PointsDistance )->setAction( mActionRectangle3PointsDistance );
4521   mMapTools->mapTool( QgsAppMapTools::Rectangle3PointsProjected )->setAction( mActionRectangle3PointsProjected );
4522   mMapTools->mapTool( QgsAppMapTools::RegularPolygon2Points )->setAction( mActionRegularPolygon2Points );
4523   mMapTools->mapTool( QgsAppMapTools::RegularPolygonCenterPoint )->setAction( mActionRegularPolygonCenterPoint );
4524   mMapTools->mapTool( QgsAppMapTools::RegularPolygonCenterCorner )->setAction( mActionRegularPolygonCenterCorner );
4525   mMapTools->mapTool( QgsAppMapTools::MoveFeature )->setAction( mActionMoveFeature );
4526   mMapTools->mapTool( QgsAppMapTools::MoveFeatureCopy )->setAction( mActionMoveFeatureCopy );
4527   mMapTools->mapTool( QgsAppMapTools::RotateFeature )->setAction( mActionRotateFeature );
4528   mMapTools->mapTool( QgsAppMapTools::ScaleFeature )->setAction( mActionScaleFeature );
4529   mMapTools->mapTool( QgsAppMapTools::OffsetCurve )->setAction( mActionOffsetCurve );
4530   mMapTools->mapTool( QgsAppMapTools::ReshapeFeatures )->setAction( mActionReshapeFeatures );
4531   mMapTools->mapTool( QgsAppMapTools::ReverseLine )->setAction( mActionReverseLine );
4532   mMapTools->mapTool( QgsAppMapTools::SplitFeatures )->setAction( mActionSplitFeatures );
4533   mMapTools->mapTool( QgsAppMapTools::SplitParts )->setAction( mActionSplitParts );
4534   mMapTools->mapTool( QgsAppMapTools::SelectFeatures )->setAction( mActionSelectFeatures );
4535   mMapTools->mapTool<QgsMapToolSelect>( QgsAppMapTools::SelectFeatures )->setSelectionMode( QgsMapToolSelectionHandler::SelectSimple );
4536   connect( mMapTools->mapTool<QgsMapToolSelect>( QgsAppMapTools::SelectFeatures ), &QgsMapToolSelect::modeChanged, this, &QgisApp::selectionModeChanged );
4537   mMapTools->mapTool( QgsAppMapTools::SelectPolygon )->setAction( mActionSelectPolygon );
4538   mMapTools->mapTool<QgsMapToolSelect>( QgsAppMapTools::SelectPolygon )->setSelectionMode( QgsMapToolSelectionHandler::SelectPolygon );
4539   connect( mMapTools->mapTool<QgsMapToolSelect>( QgsAppMapTools::SelectPolygon ), &QgsMapToolSelect::modeChanged, this, &QgisApp::selectionModeChanged );
4540   mMapTools->mapTool( QgsAppMapTools::SelectFreehand )->setAction( mActionSelectFreehand );
4541   mMapTools->mapTool<QgsMapToolSelect>( QgsAppMapTools::SelectFreehand )->setSelectionMode( QgsMapToolSelectionHandler::SelectFreehand );
4542   connect( mMapTools->mapTool<QgsMapToolSelect>( QgsAppMapTools::SelectFreehand ), &QgsMapToolSelect::modeChanged, this, &QgisApp::selectionModeChanged );
4543   mMapTools->mapTool( QgsAppMapTools::SelectRadius )->setAction( mActionSelectRadius );
4544   mMapTools->mapTool<QgsMapToolSelect>( QgsAppMapTools::SelectRadius )->setSelectionMode( QgsMapToolSelectionHandler::SelectRadius );
4545   connect( mMapTools->mapTool<QgsMapToolSelect>( QgsAppMapTools::SelectRadius ), &QgsMapToolSelect::modeChanged, this, &QgisApp::selectionModeChanged );
4546   mMapTools->mapTool( QgsAppMapTools::AddRing )->setAction( mActionAddRing );
4547   mMapTools->mapTool( QgsAppMapTools::FillRing )->setAction( mActionFillRing );
4548   mMapTools->mapTool( QgsAppMapTools::AddPart )->setAction( mActionAddPart );
4549   mMapTools->mapTool( QgsAppMapTools::SimplifyFeature )->setAction( mActionSimplifyFeature );
4550   mMapTools->mapTool( QgsAppMapTools::DeleteRing )->setAction( mActionDeleteRing );
4551   mMapTools->mapTool( QgsAppMapTools::DeletePart )->setAction( mActionDeletePart );
4552   mMapTools->mapTool( QgsAppMapTools::VertexTool )->setAction( mActionVertexTool );
4553   mMapTools->mapTool( QgsAppMapTools::VertexToolActiveLayer )->setAction( mActionVertexToolActiveLayer );
4554   mMapTools->mapTool( QgsAppMapTools::RotatePointSymbolsTool )->setAction( mActionRotatePointSymbols );
4555   mMapTools->mapTool( QgsAppMapTools::OffsetPointSymbolTool )->setAction( mActionOffsetPointSymbol );
4556   mMapTools->mapTool( QgsAppMapTools::TrimExtendFeature )->setAction( mActionTrimExtendFeature );
4557   mMapTools->mapTool( QgsAppMapTools::PinLabels )->setAction( mActionPinLabels );
4558   mMapTools->mapTool( QgsAppMapTools::ShowHideLabels )->setAction( mActionShowHideLabels );
4559   mMapTools->mapTool( QgsAppMapTools::MoveLabel )->setAction( mActionMoveLabel );
4560   mMapTools->mapTool( QgsAppMapTools::RotateLabel )->setAction( mActionRotateLabel );
4561   mMapTools->mapTool( QgsAppMapTools::ChangeLabelProperties )->setAction( mActionChangeLabelProperties );
4562   mMapTools->mapTool( QgsAppMapTools::AnnotationEdit )->setAction( mActionModifyAnnotation );
4563 
4564   //ensure that non edit tool is initialized or we will get crashes in some situations
4565   mNonEditMapTool = mMapTools->mapTool( QgsAppMapTools::Pan );
4566 }
4567 
createOverview()4568 void QgisApp::createOverview()
4569 {
4570   // overview canvas
4571   mOverviewCanvas = new QgsMapOverviewCanvas( nullptr, mMapCanvas );
4572 
4573   //set canvas color to default
4574   QgsSettings settings;
4575   int red = settings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
4576   int green = settings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
4577   int blue = settings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
4578   mOverviewCanvas->setBackgroundColor( QColor( red, green, blue ) );
4579 
4580   mOverviewMapCursor = new QCursor( Qt::OpenHandCursor );
4581   mOverviewCanvas->setCursor( *mOverviewMapCursor );
4582 //  QVBoxLayout *myOverviewLayout = new QVBoxLayout;
4583 //  myOverviewLayout->addWidget(overviewCanvas);
4584 //  overviewFrame->setLayout(myOverviewLayout);
4585   mOverviewDock = new QgsDockWidget( tr( "Overview" ), this );
4586 
4587   QShortcut *showOverviewDock = new QShortcut( QKeySequence( tr( "Ctrl+8" ) ), this );
4588   connect( showOverviewDock, &QShortcut::activated, mOverviewDock, &QgsDockWidget::toggleUserVisible );
4589   showOverviewDock->setObjectName( QStringLiteral( "ShowOverviewPanel" ) );
4590   showOverviewDock->setWhatsThis( tr( "Show Overview Panel" ) );
4591 
4592   mOverviewDock->setObjectName( QStringLiteral( "Overview" ) );
4593   mOverviewDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
4594   mOverviewDock->setWidget( mOverviewCanvas );
4595   addDockWidget( Qt::LeftDockWidgetArea, mOverviewDock );
4596   // add to the Panel submenu
4597   mPanelMenu->addAction( mOverviewDock->toggleViewAction() );
4598 
4599   mLayerTreeCanvasBridge->setOverviewCanvas( mOverviewCanvas );
4600 }
4601 
addDockWidget(Qt::DockWidgetArea area,QDockWidget * thepDockWidget)4602 void QgisApp::addDockWidget( Qt::DockWidgetArea area, QDockWidget *thepDockWidget )
4603 {
4604   QMainWindow::addDockWidget( area, thepDockWidget );
4605   // Make the right and left docks consume all vertical space and top
4606   // and bottom docks nest between them
4607   setCorner( Qt::TopLeftCorner, Qt::LeftDockWidgetArea );
4608   setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea );
4609   setCorner( Qt::TopRightCorner, Qt::RightDockWidgetArea );
4610   setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea );
4611   // add to the Panel submenu
4612   mPanelMenu->addAction( thepDockWidget->toggleViewAction() );
4613 
4614   thepDockWidget->show();
4615 
4616   // refresh the map canvas
4617   refreshMapCanvas();
4618 }
4619 
removeDockWidget(QDockWidget * thepDockWidget)4620 void QgisApp::removeDockWidget( QDockWidget *thepDockWidget )
4621 {
4622   QMainWindow::removeDockWidget( thepDockWidget );
4623   mPanelMenu->removeAction( thepDockWidget->toggleViewAction() );
4624 }
4625 
addToolBar(const QString & name)4626 QToolBar *QgisApp::addToolBar( const QString &name )
4627 {
4628   QToolBar *toolBar = QMainWindow::addToolBar( name );
4629   // add to the Toolbar submenu
4630   mToolbarMenu->addAction( toolBar->toggleViewAction() );
4631   return toolBar;
4632 }
4633 
addToolBar(QToolBar * toolBar,Qt::ToolBarArea area)4634 void QgisApp::addToolBar( QToolBar *toolBar, Qt::ToolBarArea area )
4635 {
4636   QMainWindow::addToolBar( area, toolBar );
4637   // add to the Toolbar submenu
4638   mToolbarMenu->addAction( toolBar->toggleViewAction() );
4639 }
4640 
layerTreeView()4641 QgsLayerTreeView *QgisApp::layerTreeView()
4642 {
4643   Q_ASSERT( mLayerTreeView );
4644   return mLayerTreeView;
4645 }
4646 
pluginManager()4647 QgsPluginManager *QgisApp::pluginManager()
4648 {
4649   Q_ASSERT( mPluginManager );
4650   return mPluginManager;
4651 }
4652 
userProfileManager()4653 QgsUserProfileManager *QgisApp::userProfileManager()
4654 {
4655   Q_ASSERT( mUserProfileManager );
4656   return mUserProfileManager;
4657 }
4658 
mapCanvas()4659 QgsMapCanvas *QgisApp::mapCanvas()
4660 {
4661   Q_ASSERT( mMapCanvas );
4662   return mMapCanvas;
4663 }
4664 
createNewMapCanvas(const QString & name)4665 QgsMapCanvas *QgisApp::createNewMapCanvas( const QString &name )
4666 {
4667   QgsMapCanvasDockWidget *dock = createNewMapCanvasDock( name );
4668   if ( !dock )
4669     return nullptr;
4670 
4671   setupDockWidget( dock );  // use default dock position settings
4672 
4673   dock->mapCanvas()->setLayers( mMapCanvas->layers() );
4674   dock->mapCanvas()->setExtent( mMapCanvas->extent() );
4675   QgsDebugMsgLevel( QStringLiteral( "QgisApp::createNewMapCanvas -2- : QgsProject::instance()->crs().description[%1]ellipsoid[%2]" ).arg( QgsProject::instance()->crs().description(), QgsProject::instance()->crs().ellipsoidAcronym() ), 3 );
4676   dock->mapCanvas()->setDestinationCrs( QgsProject::instance()->crs() );
4677   dock->mapCanvas()->freeze( false );
4678   return dock->mapCanvas();
4679 }
4680 
createNewMapCanvasDock(const QString & name)4681 QgsMapCanvasDockWidget *QgisApp::createNewMapCanvasDock( const QString &name )
4682 {
4683   const auto canvases = mapCanvases();
4684   for ( QgsMapCanvas *canvas : canvases )
4685   {
4686     if ( canvas->objectName() == name )
4687     {
4688       QgsDebugMsg( QStringLiteral( "A map canvas with name '%1' already exists!" ).arg( name ) );
4689       return nullptr;
4690     }
4691   }
4692 
4693   QgsMapCanvasDockWidget *mapCanvasWidget = new QgsMapCanvasDockWidget( name, this );
4694   mapCanvasWidget->setAllowedAreas( Qt::AllDockWidgetAreas );
4695   mapCanvasWidget->setMainCanvas( mMapCanvas );
4696 
4697   QgsMapCanvas *mapCanvas = mapCanvasWidget->mapCanvas();
4698   mapCanvas->freeze( true );
4699   mapCanvas->setObjectName( name );
4700   mapCanvas->setProject( QgsProject::instance() );
4701   connect( mapCanvas, &QgsMapCanvas::messageEmitted, this, &QgisApp::displayMessage );
4702   connect( mLayerTreeCanvasBridge, &QgsLayerTreeMapCanvasBridge::canvasLayersChanged, mapCanvas, &QgsMapCanvas::setLayers );
4703 
4704   applyProjectSettingsToCanvas( mapCanvas );
4705   applyDefaultSettingsToCanvas( mapCanvas );
4706 
4707   // add existing annotations to canvas
4708   const auto constAnnotations = QgsProject::instance()->annotationManager()->annotations();
4709   for ( QgsAnnotation *annotation : constAnnotations )
4710   {
4711     QgsMapCanvasAnnotationItem *canvasItem = new QgsMapCanvasAnnotationItem( annotation, mapCanvas );
4712     Q_UNUSED( canvasItem ) //item is already added automatically to canvas scene
4713   }
4714 
4715   mapCanvas->setCustomDropHandlers( mCustomDropHandlers );
4716 
4717   markDirty();
4718   connect( mapCanvasWidget, &QgsMapCanvasDockWidget::closed, this, &QgisApp::markDirty );
4719   connect( mapCanvasWidget, &QgsMapCanvasDockWidget::renameTriggered, this, &QgisApp::renameView );
4720 
4721   return mapCanvasWidget;
4722 }
4723 
4724 
setupDockWidget(QDockWidget * dockWidget,bool isFloating,QRect dockGeometry,Qt::DockWidgetArea area)4725 void QgisApp::setupDockWidget( QDockWidget *dockWidget, bool isFloating, QRect dockGeometry, Qt::DockWidgetArea area )
4726 {
4727   dockWidget->setFloating( isFloating );
4728   if ( dockGeometry.isEmpty() )
4729   {
4730     // try to guess a nice initial placement for view - about 3/4 along, half way down
4731     dockWidget->setGeometry( QRect( static_cast< int >( rect().width() * 0.75 ), static_cast< int >( rect().height() * 0.5 ), 400, 400 ) );
4732     addDockWidget( area, dockWidget );
4733   }
4734   else
4735   {
4736     if ( !isFloating )
4737     {
4738       // ugly hack, but only way to set dock size correctly for Qt < 5.6
4739       dockWidget->setFixedSize( dockGeometry.size() );
4740       addDockWidget( area, dockWidget );
4741       dockWidget->resize( dockGeometry.size() );
4742       QgsApplication::processEvents(); // required!
4743       dockWidget->setFixedSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX );
4744     }
4745     else
4746     {
4747       dockWidget->setGeometry( dockGeometry );
4748       addDockWidget( area, dockWidget );
4749     }
4750   }
4751 }
4752 
closeMapCanvas(const QString & name)4753 void QgisApp::closeMapCanvas( const QString &name )
4754 {
4755   const auto dockWidgets = findChildren< QgsMapCanvasDockWidget * >();
4756   for ( QgsMapCanvasDockWidget *w : dockWidgets )
4757   {
4758     if ( w->mapCanvas()->objectName() == name )
4759     {
4760       w->close();
4761       delete w;
4762       break;
4763     }
4764   }
4765 }
4766 
closeAdditionalMapCanvases()4767 void QgisApp::closeAdditionalMapCanvases()
4768 {
4769   QgsCanvasRefreshBlocker refreshBlocker; // closing docks may cause canvases to resize, and we don't want a map refresh occurring
4770   const auto dockWidgets = findChildren< QgsMapCanvasDockWidget * >();
4771   for ( QgsMapCanvasDockWidget *w : dockWidgets )
4772   {
4773     w->close();
4774     delete w;
4775   }
4776 }
4777 
closeAdditional3DMapCanvases()4778 void QgisApp::closeAdditional3DMapCanvases()
4779 {
4780 #ifdef HAVE_3D
4781   const QList< Qgs3DMapCanvasDockWidget * > canvases = findChildren< Qgs3DMapCanvasDockWidget * >();
4782   for ( Qgs3DMapCanvasDockWidget *w : canvases )
4783   {
4784     w->close();
4785     delete w;
4786   }
4787 #endif
4788 }
4789 
freezeCanvases(bool frozen)4790 void QgisApp::freezeCanvases( bool frozen )
4791 {
4792   const auto canvases = mapCanvases();
4793   for ( QgsMapCanvas *canvas : canvases )
4794   {
4795     canvas->freeze( frozen );
4796   }
4797 }
4798 
messageBar()4799 QgsMessageBar *QgisApp::messageBar()
4800 {
4801   // Q_ASSERT( mInfoBar );
4802   return mInfoBar;
4803 }
4804 
toggleLogMessageIcon(bool hasLogMessage)4805 void QgisApp::toggleLogMessageIcon( bool hasLogMessage )
4806 {
4807   if ( hasLogMessage && !mLogDock->isVisible() )
4808   {
4809     mMessageButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mMessageLog.svg" ) ) );
4810   }
4811   else
4812   {
4813     mMessageButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mMessageLogRead.svg" ) ) );
4814   }
4815 }
4816 
openMessageLog()4817 void QgisApp::openMessageLog()
4818 {
4819   mLogDock->setUserVisible( true );
4820 }
4821 
addUserInputWidget(QWidget * widget)4822 void QgisApp::addUserInputWidget( QWidget *widget )
4823 {
4824   mUserInputDockWidget->addUserInputWidget( widget );
4825 }
4826 
initLayerTreeView()4827 void QgisApp::initLayerTreeView()
4828 {
4829   mLayerTreeDock = new QgsDockWidget( tr( "Layers" ), this );
4830   mLayerTreeDock->setObjectName( QStringLiteral( "Layers" ) );
4831   mLayerTreeDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
4832 
4833   QShortcut *showLayersTreeDock = new QShortcut( QKeySequence( tr( "Ctrl+1" ) ), this );
4834   connect( showLayersTreeDock, &QShortcut::activated, mLayerTreeDock, &QgsDockWidget::toggleUserVisible );
4835   showLayersTreeDock->setObjectName( QStringLiteral( "ShowLayersPanel" ) );
4836   showLayersTreeDock->setWhatsThis( tr( "Show Layers Panel" ) );
4837 
4838   QgsLayerTreeModel *model = new QgsLayerTreeModel( QgsProject::instance()->layerTreeRoot(), this );
4839 #ifdef ENABLE_MODELTEST
4840   new ModelTest( model, this );
4841 #endif
4842   model->setFlag( QgsLayerTreeModel::AllowNodeReorder );
4843   model->setFlag( QgsLayerTreeModel::AllowNodeRename );
4844   model->setFlag( QgsLayerTreeModel::AllowNodeChangeVisibility );
4845   model->setFlag( QgsLayerTreeModel::ShowLegendAsTree );
4846   model->setFlag( QgsLayerTreeModel::UseEmbeddedWidgets );
4847   model->setFlag( QgsLayerTreeModel::UseTextFormatting );
4848   model->setAutoCollapseLegendNodes( 10 );
4849 
4850   mLayerTreeView->setModel( model );
4851   mLayerTreeView->setMessageBar( mInfoBar );
4852 
4853   mLayerTreeView->setMenuProvider( new QgsAppLayerTreeViewMenuProvider( mLayerTreeView, mMapCanvas ) );
4854   new QgsLayerTreeViewFilterIndicatorProvider( mLayerTreeView );  // gets parented to the layer view
4855   new QgsLayerTreeViewEmbeddedIndicatorProvider( mLayerTreeView );  // gets parented to the layer view
4856   new QgsLayerTreeViewMemoryIndicatorProvider( mLayerTreeView );  // gets parented to the layer view
4857   new QgsLayerTreeViewNotesIndicatorProvider( mLayerTreeView );  // gets parented to the layer view
4858   new QgsLayerTreeViewTemporalIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
4859   new QgsLayerTreeViewNoCrsIndicatorProvider( mLayerTreeView );  // gets parented to the layer view
4860   new QgsLayerTreeViewOfflineIndicatorProvider( mLayerTreeView );  // gets parented to the layer view
4861   new QgsLayerTreeViewLowAccuracyIndicatorProvider( mLayerTreeView );  // gets parented to the layer view
4862   QgsLayerTreeViewBadLayerIndicatorProvider *badLayerIndicatorProvider = new QgsLayerTreeViewBadLayerIndicatorProvider( mLayerTreeView );  // gets parented to the layer view
4863   connect( badLayerIndicatorProvider, &QgsLayerTreeViewBadLayerIndicatorProvider::requestChangeDataSource, this, &QgisApp::changeDataSource );
4864   new QgsLayerTreeViewNonRemovableIndicatorProvider( mLayerTreeView );  // gets parented to the layer view
4865 
4866   setupLayerTreeViewFromSettings();
4867 
4868   connect( mLayerTreeView, &QAbstractItemView::doubleClicked, this, &QgisApp::layerTreeViewDoubleClicked );
4869   connect( mLayerTreeView, &QgsLayerTreeView::currentLayerChanged, this, &QgisApp::onActiveLayerChanged );
4870   connect( mLayerTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgisApp::updateNewLayerInsertionPoint );
4871   connect( QgsProject::instance()->layerTreeRegistryBridge(), &QgsLayerTreeRegistryBridge::addedLayersToLayerTree,
4872            this, &QgisApp::autoSelectAddedLayer );
4873 
4874   // add group action
4875   QAction *actionAddGroup = new QAction( tr( "Add Group" ), this );
4876   actionAddGroup->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddGroup.svg" ) ) );
4877   actionAddGroup->setToolTip( tr( "Add Group" ) );
4878   connect( actionAddGroup, &QAction::triggered, mLayerTreeView->defaultActions(), &QgsLayerTreeViewDefaultActions::addGroup );
4879 
4880   // visibility groups tool button
4881   QToolButton *btnVisibilityPresets = new QToolButton;
4882   btnVisibilityPresets->setAutoRaise( true );
4883   btnVisibilityPresets->setToolTip( tr( "Manage Map Themes" ) );
4884   btnVisibilityPresets->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowAllLayers.svg" ) ) );
4885   btnVisibilityPresets->setPopupMode( QToolButton::InstantPopup );
4886   btnVisibilityPresets->setMenu( QgsMapThemes::instance()->menu() );
4887 
4888   // filter legend actions
4889   mFilterLegendToolButton = new QToolButton( this );
4890   mFilterLegendToolButton->setAutoRaise( true );
4891   mFilterLegendToolButton->setToolTip( tr( "Filter Legend" ) );
4892   mFilterLegendToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFilter2.svg" ) ) );
4893   mFilterLegendToolButton->setPopupMode( QToolButton::InstantPopup );
4894   QMenu *filterLegendMenu = new QMenu( this );
4895   mFilterLegendToolButton->setMenu( filterLegendMenu );
4896   mFilterLegendByMapContentAction = new QAction( tr( "Filter Legend by Map Content" ), this );
4897   mFilterLegendByMapContentAction->setCheckable( true );
4898   connect( mFilterLegendByMapContentAction, &QAction::toggled, this, &QgisApp::updateFilterLegend );
4899   filterLegendMenu->addAction( mFilterLegendByMapContentAction );
4900 
4901   mFilterLegendToggleShowPrivateLayersAction = new QAction( tr( "Show Private Layers" ), this );
4902   mFilterLegendToggleShowPrivateLayersAction->setCheckable( true );
4903   connect( mFilterLegendToggleShowPrivateLayersAction, &QAction::toggled, this, [ = ]( bool showPrivateLayers ) { layerTreeView()->setShowPrivateLayers( showPrivateLayers ); } );
4904   filterLegendMenu->addAction( mFilterLegendToggleShowPrivateLayersAction );
4905 
4906   mLegendExpressionFilterButton = new QgsLegendFilterButton( this );
4907   mLegendExpressionFilterButton->setToolTip( tr( "Filter legend by expression" ) );
4908   connect( mLegendExpressionFilterButton, &QAbstractButton::toggled, this, &QgisApp::toggleFilterLegendByExpression );
4909 
4910   mActionStyleDock = new QAction( tr( "Layer Styling" ), this );
4911   mActionStyleDock->setCheckable( true );
4912   mActionStyleDock->setToolTip( tr( "Open the Layer Styling panel" ) );
4913   mActionStyleDock->setShortcut( QStringLiteral( "F7" ) );
4914   mActionStyleDock->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/symbology.svg" ) ) );
4915   connect( mActionStyleDock, &QAction::toggled, this, &QgisApp::mapStyleDock );
4916 
4917   // expand / collapse tool buttons
4918   QAction *actionExpandAll = new QAction( tr( "Expand All" ), this );
4919   actionExpandAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionExpandTree.svg" ) ) );
4920   actionExpandAll->setToolTip( tr( "Expand All" ) );
4921   connect( actionExpandAll, &QAction::triggered, mLayerTreeView, &QgsLayerTreeView::expandAllNodes );
4922   QAction *actionCollapseAll = new QAction( tr( "Collapse All" ), this );
4923   actionCollapseAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCollapseTree.svg" ) ) );
4924   actionCollapseAll->setToolTip( tr( "Collapse All" ) );
4925   connect( actionCollapseAll, &QAction::triggered, mLayerTreeView, &QgsLayerTreeView::collapseAllNodes );
4926 
4927   QToolBar *toolbar = new QToolBar();
4928   toolbar->setIconSize( iconSize( true ) );
4929   toolbar->addAction( mActionStyleDock );
4930   toolbar->addAction( actionAddGroup );
4931   toolbar->addWidget( btnVisibilityPresets );
4932   toolbar->addWidget( mFilterLegendToolButton );
4933   toolbar->addWidget( mLegendExpressionFilterButton );
4934   toolbar->addAction( actionExpandAll );
4935   toolbar->addAction( actionCollapseAll );
4936   toolbar->addAction( mActionRemoveLayer );
4937 
4938   QVBoxLayout *vboxLayout = new QVBoxLayout;
4939   vboxLayout->setContentsMargins( 0, 0, 0, 0 );
4940   vboxLayout->setSpacing( 0 );
4941   vboxLayout->addWidget( toolbar );
4942   vboxLayout->addWidget( mLayerTreeView );
4943 
4944   QWidget *w = new QWidget;
4945   w->setLayout( vboxLayout );
4946   mLayerTreeDock->setWidget( w );
4947   addDockWidget( Qt::LeftDockWidgetArea, mLayerTreeDock );
4948 
4949   mLayerTreeCanvasBridge = new QgsLayerTreeMapCanvasBridge( QgsProject::instance()->layerTreeRoot(), mMapCanvas, this );
4950 
4951   mMapLayerOrder = new QgsCustomLayerOrderWidget( mLayerTreeCanvasBridge, this );
4952   mMapLayerOrder->setObjectName( QStringLiteral( "theMapLayerOrder" ) );
4953 
4954   mLayerOrderDock = new QgsDockWidget( tr( "Layer Order" ), this );
4955   mLayerOrderDock->setObjectName( QStringLiteral( "LayerOrder" ) );
4956   mLayerOrderDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
4957 
4958   QShortcut *showLayerOrderDock = new QShortcut( QKeySequence( tr( "Ctrl+9" ) ), this );
4959   connect( showLayerOrderDock, &QShortcut::activated, mLayerOrderDock, &QgsDockWidget::toggleUserVisible );
4960   showLayerOrderDock->setObjectName( QStringLiteral( "ShowLayerOrderPanel" ) );
4961   showLayerOrderDock->setWhatsThis( tr( "Show Layer Order Panel" ) );
4962 
4963   mLayerOrderDock->setWidget( mMapLayerOrder );
4964   addDockWidget( Qt::LeftDockWidgetArea, mLayerOrderDock );
4965   mLayerOrderDock->hide();
4966 
4967   connect( mMapCanvas, &QgsMapCanvas::mapCanvasRefreshed, this, &QgisApp::updateFilterLegend );
4968   connect( mMapCanvas, &QgsMapCanvas::renderErrorOccurred, badLayerIndicatorProvider, &QgsLayerTreeViewBadLayerIndicatorProvider::reportLayerError );
4969 }
4970 
setupLayerTreeViewFromSettings()4971 void QgisApp::setupLayerTreeViewFromSettings()
4972 {
4973   QgsSettings s;
4974 
4975   QgsLayerTreeModel *model = mLayerTreeView->layerTreeModel();
4976   QFont fontLayer, fontGroup;
4977   fontLayer.setBold( true );
4978   fontGroup.setBold( false );
4979   model->setLayerTreeNodeFont( QgsLayerTreeNode::NodeLayer, fontLayer );
4980   model->setLayerTreeNodeFont( QgsLayerTreeNode::NodeGroup, fontGroup );
4981 }
4982 
4983 
updateNewLayerInsertionPoint()4984 void QgisApp::updateNewLayerInsertionPoint()
4985 {
4986   QgsLayerTreeRegistryBridge::InsertionPoint insertionPoint = layerTreeInsertionPoint();
4987   QgsProject::instance()->layerTreeRegistryBridge()->setLayerInsertionPoint( insertionPoint );
4988 }
4989 
layerTreeInsertionPoint() const4990 QgsLayerTreeRegistryBridge::InsertionPoint QgisApp::layerTreeInsertionPoint() const
4991 {
4992   // defaults
4993   QgsLayerTreeGroup *insertGroup = mLayerTreeView->layerTreeModel()->rootGroup();
4994   QModelIndex current = mLayerTreeView->currentIndex();
4995 
4996   int index = 0;
4997 
4998   if ( current.isValid() )
4999   {
5000     index = current.row();
5001 
5002     QgsLayerTreeNode *currentNode = mLayerTreeView->currentNode();
5003     if ( currentNode )
5004     {
5005       // if the insertion point is actually a group, insert new layers into the group
5006       if ( QgsLayerTree::isGroup( currentNode ) )
5007       {
5008         // if the group is embedded go to the first non-embedded group, at worst the top level item
5009         QgsLayerTreeGroup *insertGroup = QgsLayerTreeUtils::firstGroupWithoutCustomProperty( QgsLayerTree::toGroup( currentNode ), QStringLiteral( "embedded" ) );
5010 
5011         return QgsLayerTreeRegistryBridge::InsertionPoint( insertGroup, 0 );
5012       }
5013 
5014       // otherwise just set the insertion point in front of the current node
5015       QgsLayerTreeNode *parentNode = currentNode->parent();
5016       if ( QgsLayerTree::isGroup( parentNode ) )
5017       {
5018         // if the group is embedded go to the first non-embedded group, at worst the top level item
5019         QgsLayerTreeGroup *parentGroup = QgsLayerTree::toGroup( parentNode );
5020         insertGroup = QgsLayerTreeUtils::firstGroupWithoutCustomProperty( parentGroup, QStringLiteral( "embedded" ) );
5021         if ( parentGroup != insertGroup )
5022           index = 0;
5023       }
5024     }
5025   }
5026   return QgsLayerTreeRegistryBridge::InsertionPoint( insertGroup, index );
5027 }
5028 
setGpsPanelConnection(QgsGpsConnection * connection)5029 void QgisApp::setGpsPanelConnection( QgsGpsConnection *connection )
5030 {
5031   mpGpsWidget->setConnection( connection );
5032 }
5033 
autoSelectAddedLayer(QList<QgsMapLayer * > layers)5034 void QgisApp::autoSelectAddedLayer( QList<QgsMapLayer *> layers )
5035 {
5036   if ( !layers.isEmpty() )
5037   {
5038     QgsLayerTreeLayer *nodeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( layers[0]->id() );
5039 
5040     if ( !nodeLayer )
5041       return;
5042 
5043     QModelIndex index = mLayerTreeView->node2index( nodeLayer );
5044     mLayerTreeView->setCurrentIndex( index );
5045   }
5046 }
5047 
createMapTips()5048 void QgisApp::createMapTips()
5049 {
5050   // Set up the timer for maptips. The timer is reset every time the mouse is moved
5051   mpMapTipsTimer = new QTimer( mMapCanvas );
5052   // connect the timer to the maptips slot
5053   connect( mpMapTipsTimer, &QTimer::timeout, this, &QgisApp::showMapTip );
5054   // set the delay to 0.850 seconds or time defined in the Settings
5055   // timer will be started next time the mouse moves
5056   QgsSettings settings;
5057   int timerInterval = settings.value( QStringLiteral( "qgis/mapTipsDelay" ), 850 ).toInt();
5058   mpMapTipsTimer->setInterval( timerInterval );
5059   mpMapTipsTimer->setSingleShot( true );
5060 
5061   // Create the maptips object
5062   mpMaptip = new QgsMapTip();
5063 }
5064 
setMapTipsDelay(int timerInterval)5065 void QgisApp::setMapTipsDelay( int timerInterval )
5066 {
5067   mpMapTipsTimer->setInterval( timerInterval );
5068 }
5069 
createDecorations()5070 void QgisApp::createDecorations()
5071 {
5072   QgsDecorationTitle *decorationTitle = new QgsDecorationTitle( this );
5073   connect( mActionDecorationTitle, &QAction::triggered, decorationTitle, &QgsDecorationTitle::run );
5074 
5075   QgsDecorationCopyright *decorationCopyright = new QgsDecorationCopyright( this );
5076   connect( mActionDecorationCopyright, &QAction::triggered, decorationCopyright, &QgsDecorationCopyright::run );
5077 
5078   QgsDecorationImage *decorationImage = new QgsDecorationImage( this );
5079   connect( mActionDecorationImage, &QAction::triggered, decorationImage, &QgsDecorationImage::run );
5080 
5081   QgsDecorationNorthArrow *decorationNorthArrow = new QgsDecorationNorthArrow( this );
5082   connect( mActionDecorationNorthArrow, &QAction::triggered, decorationNorthArrow, &QgsDecorationNorthArrow::run );
5083 
5084   QgsDecorationScaleBar *decorationScaleBar = new QgsDecorationScaleBar( this );
5085   connect( mActionDecorationScaleBar, &QAction::triggered, decorationScaleBar, &QgsDecorationScaleBar::run );
5086 
5087   QgsDecorationGrid *decorationGrid = new QgsDecorationGrid( this );
5088   connect( mActionDecorationGrid, &QAction::triggered, decorationGrid, &QgsDecorationGrid::run );
5089 
5090   QgsDecorationLayoutExtent *decorationLayoutExtent = new QgsDecorationLayoutExtent( this );
5091   connect( mActionDecorationLayoutExtent, &QAction::triggered, decorationLayoutExtent, &QgsDecorationLayoutExtent::run );
5092 
5093   // add the decorations in a particular order so they are rendered in that order
5094   addDecorationItem( decorationGrid );
5095   addDecorationItem( decorationImage );
5096   addDecorationItem( decorationTitle );
5097   addDecorationItem( decorationCopyright );
5098   addDecorationItem( decorationNorthArrow );
5099   addDecorationItem( decorationScaleBar );
5100   addDecorationItem( decorationLayoutExtent );
5101   connect( mMapCanvas, &QgsMapCanvas::renderComplete, this, &QgisApp::renderDecorationItems );
5102   connect( this, &QgisApp::newProject, this, &QgisApp::projectReadDecorationItems );
5103   connect( this, &QgisApp::projectRead, this, &QgisApp::projectReadDecorationItems );
5104 }
5105 
renderDecorationItems(QPainter * p)5106 void QgisApp::renderDecorationItems( QPainter *p )
5107 {
5108   QgsRenderContext context = QgsRenderContext::fromMapSettings( mMapCanvas->mapSettings() );
5109   context.setPainter( p );
5110 
5111   const auto constMDecorationItems = mDecorationItems;
5112   for ( QgsDecorationItem *item : constMDecorationItems )
5113   {
5114     item->render( mMapCanvas->mapSettings(), context );
5115   }
5116 }
5117 
projectReadDecorationItems()5118 void QgisApp::projectReadDecorationItems()
5119 {
5120   const auto constMDecorationItems = mDecorationItems;
5121   for ( QgsDecorationItem *item : constMDecorationItems )
5122   {
5123     item->projectRead();
5124   }
5125 }
5126 
5127 // Update project menu with the current list of recently accessed projects
updateRecentProjectPaths()5128 void QgisApp::updateRecentProjectPaths()
5129 {
5130   mRecentProjectsMenu->clear();
5131 
5132   const auto constMRecentProjects = mRecentProjects;
5133   for ( const QgsRecentProjectItemsModel::RecentProjectData &recentProject : constMRecentProjects )
5134   {
5135     QAction *action = mRecentProjectsMenu->addAction(
5136                         QStringLiteral( "%1 (%2)" )
5137                         .arg( recentProject.title != recentProject.path
5138                               ? recentProject.title
5139                               : QFileInfo( recentProject.path ).completeBaseName(), QDir::toNativeSeparators( recentProject.path )
5140                             ).replace( "&", "&&" )
5141                       );
5142 
5143     QgsProjectStorage *storage = QgsApplication::projectStorageRegistry()->projectStorageFromUri( recentProject.path );
5144 
5145     if ( storage )
5146     {
5147       QString path = storage->filePath( recentProject.path );
5148       // for geopackage projects, the path will be empty, if not valid
5149       if ( storage->type() == QLatin1String( "geopackage" ) && path.isEmpty() )
5150       {
5151         action->setEnabled( false );
5152         action->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIndicatorBadLayer.svg" ) ) );
5153       }
5154     }
5155     else
5156     {
5157       bool exists = QFile::exists( recentProject.path );
5158       action->setEnabled( exists );
5159       if ( !exists )
5160         action->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIndicatorBadLayer.svg" ) ) );
5161     }
5162 
5163     action->setData( recentProject.path );
5164     if ( recentProject.pin )
5165     {
5166       action->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/pin.svg" ) ) );
5167     }
5168   }
5169 
5170   std::vector< QgsNative::RecentProjectProperties > recentProjects;
5171   for ( const QgsRecentProjectItemsModel::RecentProjectData &recentProject : std::as_const( mRecentProjects ) )
5172   {
5173     QgsNative::RecentProjectProperties project;
5174     project.title = recentProject.title;
5175     project.fileName = QFileInfo( recentProject.path ).baseName();
5176     project.path = recentProject.path;
5177     project.name = project.title != project.path ? project.title : project.fileName;
5178     recentProjects.emplace_back( project );
5179   }
5180   QgsGui::instance()->nativePlatformInterface()->onRecentProjectsChanged( recentProjects );
5181 }
5182 
5183 // add this file to the recently opened/saved projects list
saveRecentProjectPath(bool savePreviewImage,const QIcon & iconOverlay)5184 void QgisApp::saveRecentProjectPath( bool savePreviewImage, const QIcon &iconOverlay )
5185 {
5186   // first, re-read the recent project paths. This prevents loss of recent
5187   // projects when multiple QGIS sessions are open
5188   readRecentProjects();
5189 
5190   // Get canonical absolute path
5191   QgsRecentProjectItemsModel::RecentProjectData projectData;
5192   projectData.path = QgsProject::instance()->absoluteFilePath();
5193   QString templateDirName = QgsSettings().value( QStringLiteral( "qgis/projectTemplateDir" ),
5194                             QString( QgsApplication::qgisSettingsDirPath() + "project_templates" ) ).toString();
5195 
5196   // We don't want the template path to appear in the recent projects list. Never.
5197   if ( projectData.path.startsWith( templateDirName ) )
5198     return;
5199 
5200   if ( projectData.path.isEmpty() )  // in case of custom project storage
5201     projectData.path = !QgsProject::instance()->fileName().isEmpty() ? QgsProject::instance()->fileName() : QgsProject::instance()->originalPath();
5202   projectData.title = QgsProject::instance()->title();
5203   if ( projectData.title.isEmpty() )
5204     projectData.title = !QgsProject::instance()->baseName().isEmpty() ? QgsProject::instance()->baseName() : QFileInfo( QgsProject::instance()->originalPath() ).completeBaseName();
5205 
5206   projectData.crs = QgsProject::instance()->crs().authid();
5207 
5208   int idx = mRecentProjects.indexOf( projectData );
5209   if ( idx != -1 )
5210     projectData.pin = mRecentProjects.at( idx ).pin;
5211 
5212   if ( savePreviewImage )
5213   {
5214     // Generate a unique file name
5215     QString fileName( QCryptographicHash::hash( ( projectData.path.toUtf8() ), QCryptographicHash::Md5 ).toHex() );
5216     QString previewDir = QStringLiteral( "%1/previewImages" ).arg( QgsApplication::qgisSettingsDirPath() );
5217     projectData.previewImagePath = QStringLiteral( "%1/%2.png" ).arg( previewDir, fileName );
5218     QDir().mkdir( previewDir );
5219 
5220     createPreviewImage( projectData.previewImagePath, iconOverlay );
5221   }
5222   else
5223   {
5224     if ( idx != -1 )
5225       projectData.previewImagePath = mRecentProjects.at( idx ).previewImagePath;
5226   }
5227 
5228   // Count the number of pinned items, those shouldn't affect trimming
5229   int pinnedCount = 0;
5230   int nonPinnedPos = 0;
5231   bool pinnedTop = true;
5232   for ( const QgsRecentProjectItemsModel::RecentProjectData &recentProject : std::as_const( mRecentProjects ) )
5233   {
5234     if ( recentProject.pin )
5235     {
5236       pinnedCount++;
5237       if ( pinnedTop )
5238       {
5239         nonPinnedPos++;
5240       }
5241     }
5242     else if ( pinnedTop )
5243     {
5244       pinnedTop = false;
5245     }
5246   }
5247 
5248   // If this file is already in the list, remove it
5249   mRecentProjects.removeAll( projectData );
5250 
5251   // Insert this file to the list
5252   mRecentProjects.insert( projectData.pin ? 0 : nonPinnedPos, projectData );
5253 
5254   const uint maxProjects = QgsSettings().value( QStringLiteral( "maxRecentProjects" ), 20, QgsSettings::App ).toUInt();
5255 
5256   // Keep the list to maxProjects items by trimming excess off the bottom
5257   // And remove the associated image
5258   while ( static_cast< uint >( mRecentProjects.count() ) > maxProjects + pinnedCount )
5259   {
5260     const QString previewImagePath = mRecentProjects.takeLast().previewImagePath;
5261     if ( QFileInfo::exists( previewImagePath ) )
5262       QFile( mRecentProjects.takeLast().previewImagePath ).remove();
5263   }
5264 
5265   // Persist the list
5266   saveRecentProjects();
5267 
5268   // Update menu list of paths
5269   updateRecentProjectPaths();
5270 
5271   // Update welcome page list
5272   if ( mWelcomePage )
5273     mWelcomePage->setRecentProjects( mRecentProjects );
5274 
5275 } // QgisApp::saveRecentProjectPath
5276 
5277 // Save recent projects list to settings
saveRecentProjects()5278 void QgisApp::saveRecentProjects()
5279 {
5280   QgsSettings settings;
5281 
5282   settings.remove( QStringLiteral( "/UI/recentProjects" ) );
5283   int idx = 0;
5284 
5285   const auto constMRecentProjects = mRecentProjects;
5286   for ( const QgsRecentProjectItemsModel::RecentProjectData &recentProject : constMRecentProjects )
5287   {
5288     ++idx;
5289     settings.beginGroup( QStringLiteral( "UI/recentProjects/%1" ).arg( idx ) );
5290     settings.setValue( QStringLiteral( "title" ), recentProject.title );
5291     settings.setValue( QStringLiteral( "path" ), recentProject.path );
5292     settings.setValue( QStringLiteral( "previewImage" ), recentProject.previewImagePath );
5293     settings.setValue( QStringLiteral( "crs" ), recentProject.crs );
5294     settings.setValue( QStringLiteral( "pin" ), recentProject.pin );
5295     settings.endGroup();
5296   }
5297 }
5298 
5299 // Update project menu with the project templates
updateProjectFromTemplates()5300 void QgisApp::updateProjectFromTemplates()
5301 {
5302   // get list of project files in template dir
5303   QgsSettings settings;
5304   QString templateDirName = settings.value( QStringLiteral( "qgis/projectTemplateDir" ),
5305                             QString( QgsApplication::qgisSettingsDirPath() + "project_templates" ) ).toString();
5306   QDir templateDir( templateDirName );
5307   QStringList filters( QStringLiteral( "*.qgs" ) );
5308   filters << QStringLiteral( "*.qgz" );
5309   templateDir.setNameFilters( filters );
5310   QStringList templateFiles = templateDir.entryList( filters );
5311 
5312   // Remove existing entries
5313   mProjectFromTemplateMenu->clear();
5314 
5315   // Add entries
5316   const auto constTemplateFiles = templateFiles;
5317   for ( const QString &templateFile : constTemplateFiles )
5318   {
5319     mProjectFromTemplateMenu->addAction( templateFile );
5320   }
5321 
5322   // add <blank> entry, which loads a blank template (regardless of "default template")
5323   if ( settings.value( QStringLiteral( "qgis/newProjectDefault" ), QVariant( false ) ).toBool() )
5324     mProjectFromTemplateMenu->addAction( tr( "< Blank >" ) );
5325 
5326 } // QgisApp::updateProjectFromTemplates
5327 
saveWindowState()5328 void QgisApp::saveWindowState()
5329 {
5330   // store window and toolbar positions
5331   QgsSettings settings;
5332   // store the toolbar/dock widget settings using Qt4 settings API
5333   settings.setValue( QStringLiteral( "UI/state" ), saveState() );
5334 
5335   // store window geometry
5336   settings.setValue( QStringLiteral( "UI/geometry" ), saveGeometry() );
5337 
5338   QgsPluginRegistry::instance()->unloadAll();
5339 }
5340 
5341 #include "ui_defaults.h"
5342 
restoreWindowState()5343 void QgisApp::restoreWindowState()
5344 {
5345   // restore the toolbar and dock widgets positions using Qt4 settings API
5346   QgsSettings settings;
5347 #if 0
5348   // because of Qt regression: https://bugreports.qt.io/browse/QTBUG-89034
5349   // we have to wait till dialog is first shown to try to restore dock geometry or it's not correctly restored
5350   // so this code was moved to showEvent for now...
5351   if ( !restoreState( settings.value( QStringLiteral( "UI/state" ), QByteArray::fromRawData( reinterpret_cast< const char * >( defaultUIstate ), sizeof defaultUIstate ) ).toByteArray() ) )
5352   {
5353     QgsDebugMsg( QStringLiteral( "restore of UI state failed" ) );
5354   }
5355 #endif
5356 
5357   if ( settings.value( QStringLiteral( "UI/hidebrowser" ), false ).toBool() )
5358   {
5359     mBrowserWidget->hide();
5360     mBrowserWidget2->hide();
5361     settings.remove( QStringLiteral( "UI/hidebrowser" ) );
5362   }
5363 
5364   // restore window geometry
5365   if ( !restoreGeometry( settings.value( QStringLiteral( "UI/geometry" ) ).toByteArray() ) )
5366   {
5367     QgsDebugMsg( QStringLiteral( "restore of UI geometry failed" ) );
5368     // default to 80% of screen size, at 10% from top left corner
5369     resize( QDesktopWidget().availableGeometry( this ).size() * 0.8 );
5370     QSize pos = QDesktopWidget().availableGeometry( this ).size() * 0.1;
5371     move( pos.width(), pos.height() );
5372   }
5373 
5374 }
5375 ///////////// END OF GUI SETUP ROUTINES ///////////////
sponsors()5376 void QgisApp::sponsors()
5377 {
5378   QgsSettings settings;
5379   QString qgisSponsorsUrl = settings.value( QStringLiteral( "qgis/qgisSponsorsUrl" ),
5380                             tr( "https://qgis.org/en/site/about/sustaining_members.html" ) ).toString();
5381   openURL( qgisSponsorsUrl, false );
5382 }
5383 
about()5384 void QgisApp::about()
5385 {
5386   static QgsAbout *sAbt = nullptr;
5387   if ( !sAbt )
5388   {
5389     sAbt = new QgsAbout( this );
5390     QString versionString = QStringLiteral( "<html><body><div align='center'><table width='100%'>" );
5391 
5392     versionString += QStringLiteral( "<tr><td>%1</td><td>%2</td><td>" ).arg( tr( "QGIS version" ), Qgis::version() );
5393 
5394     if ( QString( Qgis::devVersion() ) == QLatin1String( "exported" ) )
5395     {
5396       versionString += tr( "QGIS code branch" );
5397       if ( Qgis::version().endsWith( QLatin1String( "Master" ) ) )
5398       {
5399         versionString += QLatin1String( "</td><td><a href=\"https://github.com/qgis/QGIS/tree/master\">master</a></td>" );
5400       }
5401       else
5402       {
5403         versionString += QStringLiteral( "</td><td><a href=\"https://github.com/qgis/QGIS/tree/release-%1_%2\">Release %1.%2</a></td>" )
5404                          .arg( Qgis::versionInt() / 10000 ).arg( Qgis::versionInt() / 100 % 100 );
5405       }
5406     }
5407     else
5408     {
5409       versionString += QStringLiteral( "%1</td><td><a href=\"https://github.com/qgis/QGIS/commit/%2\">%2</a></td>" ).arg( tr( "QGIS code revision" ) ).arg( Qgis::devVersion() );
5410     }
5411     versionString += QLatin1String( "</tr><tr>" );
5412 
5413     // Qt version
5414     const QString qtVersionCompiled{ QT_VERSION_STR };
5415     const QString qtVersionRunning{ qVersion() };
5416     if ( qtVersionCompiled != qtVersionRunning )
5417     {
5418       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Compiled against Qt" ), qtVersionCompiled );
5419       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Running against Qt" ), qtVersionRunning );
5420     }
5421     else
5422     {
5423       versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "Qt version" ), qtVersionCompiled );
5424     }
5425     versionString += QLatin1String( "</tr><tr>" );
5426 
5427     // Python version
5428     const QString pythonVersion{ PYTHON_VERSION };
5429     versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "Python version" ), PYTHON_VERSION );
5430     versionString += QLatin1String( "</tr><tr>" );
5431 
5432     // GDAL version
5433     const QString gdalVersionCompiled { GDAL_RELEASE_NAME };
5434     const QString gdalVersionRunning { GDALVersionInfo( "RELEASE_NAME" ) };
5435     if ( gdalVersionCompiled != gdalVersionRunning )
5436     {
5437       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Compiled against GDAL/OGR" ), gdalVersionCompiled );
5438       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Running against GDAL/OGR" ), gdalVersionRunning );
5439     }
5440     else
5441     {
5442       versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "GDAL/OGR version" ), gdalVersionCompiled );
5443     }
5444     versionString += QLatin1String( "</tr><tr>" );
5445 
5446     // proj
5447     PJ_INFO info = proj_info();
5448     const QString projVersionCompiled { QStringLiteral( "%1.%2.%3" ).arg( PROJ_VERSION_MAJOR ).arg( PROJ_VERSION_MINOR ).arg( PROJ_VERSION_PATCH ) };
5449     const QString projVersionRunning { info.version };
5450     if ( projVersionCompiled != projVersionRunning )
5451     {
5452       versionString += QStringLiteral( "<td>%1</td><td>%2.%3.%4</td>" ).arg( tr( "Compiled against PROJ" ), projVersionCompiled );
5453       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Running against PROJ" ), projVersionRunning );
5454     }
5455     else
5456     {
5457       versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "PROJ version" ), projVersionCompiled );
5458     }
5459     versionString += QLatin1String( "</tr><tr>" );
5460 
5461     // CRS database versions
5462     versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2 (%3)</td>" ).arg( tr( "EPSG Registry database version" ), QgsProjUtils::epsgRegistryVersion(), QgsProjUtils::epsgRegistryDate().toString( Qt::ISODate ) );
5463     versionString += QLatin1String( "</tr><tr>" );
5464 
5465     // GEOS version
5466     const QString geosVersionCompiled { GEOS_CAPI_VERSION };
5467     const QString geosVersionRunning { GEOSversion() };
5468     if ( geosVersionCompiled != geosVersionRunning )
5469     {
5470       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Compiled against GEOS" ), geosVersionCompiled );
5471       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Running against GEOS" ), geosVersionRunning );
5472     }
5473     else
5474     {
5475       versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "GEOS version" ), geosVersionCompiled );
5476     }
5477     versionString += QLatin1String( "</tr><tr>" );
5478 
5479     // SQLite version
5480     const QString sqliteVersionCompiled { SQLITE_VERSION };
5481     const QString sqliteVersionRunning { sqlite3_libversion() };
5482     if ( sqliteVersionCompiled != sqliteVersionRunning )
5483     {
5484       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Compiled against SQLite" ), sqliteVersionCompiled );
5485       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Running against SQLite" ), sqliteVersionRunning );
5486     }
5487     else
5488     {
5489       versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "SQLite version" ), sqliteVersionCompiled );
5490     }
5491     versionString += QLatin1String( "</tr><tr>" );
5492 
5493     // PDAL
5494 #ifdef HAVE_PDAL
5495     const QString pdalVersionCompiled { PDAL_VERSION };
5496 #if PDAL_VERSION_MAJOR_INT > 1 || (PDAL_VERSION_MAJOR_INT == 1 && PDAL_VERSION_MINOR_INT >= 7)
5497     const QString pdalVersionRunningRaw { QString::fromStdString( pdal::Config::fullVersionString() ) };
5498 #else
5499     const QString pdalVersionRunningRaw { QString::fromStdString( pdal::GetFullVersionString() ) };
5500 #endif
5501     const QRegularExpression pdalVersionRx { QStringLiteral( "(\\d+\\.\\d+\\.\\d+)" )};
5502     const QRegularExpressionMatch pdalVersionMatch{ pdalVersionRx.match( pdalVersionRunningRaw ) };
5503     const QString pdalVersionRunning{ pdalVersionMatch.hasMatch() ? pdalVersionMatch.captured( 1 ) : pdalVersionRunningRaw };
5504     if ( pdalVersionCompiled != pdalVersionRunning )
5505     {
5506       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Compiled against PDAL" ), pdalVersionCompiled );
5507       versionString += QStringLiteral( "<td>%1</td><td>%2</td>" ).arg( tr( "Running against PDAL" ), pdalVersionRunning );
5508     }
5509     else
5510     {
5511       versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "PDAL version" ), pdalVersionCompiled );
5512     }
5513     versionString += QLatin1String( "</tr><tr>" );
5514 #endif
5515 
5516     // postgres
5517     versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">" ).arg( tr( "PostgreSQL client version" ) );
5518 #ifdef HAVE_POSTGRESQL
5519     versionString += QStringLiteral( PG_VERSION );
5520 #else
5521     versionString += tr( "No support" );
5522 #endif
5523     versionString += QLatin1String( "</td></tr><tr>" );
5524 
5525     // spatialite
5526     versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">" ).arg( tr( "SpatiaLite version" ) );
5527 #ifdef HAVE_SPATIALITE
5528     versionString += QStringLiteral( "%1</td>" ).arg( spatialite_version() );
5529 #else
5530     versionString += tr( "No support" );
5531 #endif
5532     versionString += QLatin1String( "</td></tr><tr>" );
5533 
5534     // QWT
5535     versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "QWT version" ), QWT_VERSION_STR );
5536     versionString += QLatin1String( "</tr><tr>" );
5537 
5538     // QScintilla
5539     versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "QScintilla2 version" ), QSCINTILLA_VERSION_STR );
5540     versionString += QLatin1String( "</tr><tr>" );
5541 
5542     // Operating system
5543     versionString += QStringLiteral( "<td>%1</td><td colspan=\"3\">%2</td>" ).arg( tr( "OS version" ), QSysInfo::prettyProductName() );
5544     versionString += QLatin1String( "</tr><tr>" );
5545 
5546 #ifdef QGISDEBUG
5547     versionString += QLatin1String( "</tr><tr>" );
5548     versionString += QStringLiteral( "<td colspan=\"4\"><i>%1</i></td>" ).arg( tr( "This copy of QGIS writes debugging output." ) );
5549     versionString += QLatin1String( "</tr><tr>" );
5550 #endif
5551 
5552 #ifdef WITH_BINDINGS
5553     if ( mPythonUtils && mPythonUtils->isEnabled() )
5554     {
5555       versionString += QStringLiteral( "</tr><tr><td colspan=\"4\">%1</td>" ).arg( tr( "Active Python plugins" ) );
5556       const QStringList activePlugins = mPythonUtils->listActivePlugins();
5557       for ( const QString &plugin : activePlugins )
5558       {
5559         const QString version = mPythonUtils->getPluginMetadata( plugin, QStringLiteral( "version" ) );
5560         versionString += QStringLiteral( "</tr><tr><td>%1</td><td colspan=\"3\">%2</td>" ).arg( plugin, version );
5561       }
5562     }
5563 #endif
5564 
5565     versionString += QLatin1String( "</tr></table></div></body></html>" );
5566 
5567     sAbt->setVersion( versionString );
5568   }
5569   sAbt->show();
5570   sAbt->raise();
5571   sAbt->activateWindow();
5572 }
5573 
addLayerDefinition()5574 void QgisApp::addLayerDefinition()
5575 {
5576   QgsSettings settings;
5577   QString lastUsedDir = settings.value( QStringLiteral( "UI/lastQLRDir" ), QDir::homePath() ).toString();
5578 
5579   QString path = QFileDialog::getOpenFileName( this, QStringLiteral( "Add Layer Definition File" ), lastUsedDir, QStringLiteral( "*.qlr" ) );
5580   if ( path.isEmpty() )
5581     return;
5582 
5583   QFileInfo fi( path );
5584   settings.setValue( QStringLiteral( "UI/lastQLRDir" ), fi.path() );
5585 
5586   openLayerDefinition( path );
5587 }
5588 
crsAndFormatAdjustedLayerUri(const QString & uri,const QStringList & supportedCrs,const QStringList & supportedFormats) const5589 QString QgisApp::crsAndFormatAdjustedLayerUri( const QString &uri, const QStringList &supportedCrs, const QStringList &supportedFormats ) const
5590 {
5591   QString newuri = uri;
5592 
5593   // Adjust layer CRS to project CRS
5594   QgsCoordinateReferenceSystem testCrs;
5595   const auto constSupportedCrs = supportedCrs;
5596   for ( const QString &c : constSupportedCrs )
5597   {
5598     testCrs.createFromOgcWmsCrs( c );
5599     if ( testCrs == mMapCanvas->mapSettings().destinationCrs() )
5600     {
5601       newuri.replace( QRegExp( "crs=[^&]+" ), "crs=" + c );
5602       QgsDebugMsgLevel( QStringLiteral( "Changing layer crs to %1, new uri: %2" ).arg( c, uri ), 2 );
5603       break;
5604     }
5605   }
5606 
5607   // Use the last used image format
5608   QString lastImageEncoding = QgsSettings().value( QStringLiteral( "/qgis/lastWmsImageEncoding" ), "image/png" ).toString();
5609   const auto constSupportedFormats = supportedFormats;
5610   for ( const QString &fmt : constSupportedFormats )
5611   {
5612     if ( fmt == lastImageEncoding )
5613     {
5614       newuri.replace( QRegExp( "format=[^&]+" ), "format=" + fmt );
5615       QgsDebugMsgLevel( QStringLiteral( "Changing layer format to %1, new uri: %2" ).arg( fmt, uri ), 2 );
5616       break;
5617     }
5618   }
5619   return newuri;
5620 }
5621 
addVectorLayers(const QStringList & layerQStringList,const QString & enc,const QString & dataSourceType)5622 bool QgisApp::addVectorLayers( const QStringList &layerQStringList, const QString &enc, const QString &dataSourceType )
5623 {
5624   return addVectorLayersPrivate( layerQStringList, enc, dataSourceType );
5625 }
5626 
addVectorLayersPrivate(const QStringList & layers,const QString & enc,const QString & dataSourceType,const bool guiWarning)5627 bool QgisApp::addVectorLayersPrivate( const QStringList &layers, const QString &enc, const QString &dataSourceType, const bool guiWarning )
5628 {
5629   //note: this method ONLY supports vector layers from the OGR provider!
5630 
5631   QgsCanvasRefreshBlocker refreshBlocker;
5632 
5633   QList<QgsMapLayer *> layersToAdd;
5634   QList<QgsMapLayer *> addedLayers;
5635   QgsSettings settings;
5636   bool userAskedToAddLayers = false;
5637 
5638   for ( const QString &layerUri : layers )
5639   {
5640     const QString uri = layerUri.trimmed();
5641     QString baseName;
5642     if ( dataSourceType == QLatin1String( "file" ) )
5643     {
5644       QString srcWithoutLayername( uri );
5645       int posPipe = srcWithoutLayername.indexOf( '|' );
5646       if ( posPipe >= 0 )
5647         srcWithoutLayername.resize( posPipe );
5648       baseName = QgsProviderUtils::suggestLayerNameFromFilePath( srcWithoutLayername );
5649 
5650       // if needed prompt for zipitem layers
5651       QString vsiPrefix = QgsZipItem::vsiPrefix( uri );
5652       if ( ! uri.startsWith( QLatin1String( "/vsi" ), Qt::CaseInsensitive ) &&
5653            ( vsiPrefix == QLatin1String( "/vsizip/" ) || vsiPrefix == QLatin1String( "/vsitar/" ) ) )
5654       {
5655         if ( askUserForZipItemLayers( uri, { QgsMapLayerType::VectorLayer } ) )
5656           continue;
5657       }
5658     }
5659     else if ( dataSourceType == QLatin1String( "database" ) )
5660     {
5661       // Try to extract the database name and use it as base name
5662       // sublayers names (if any) will be appended to the layer name
5663       const QVariantMap parts( QgsProviderRegistry::instance()->decodeUri( QStringLiteral( "ogr" ), uri ) );
5664       if ( parts.value( QStringLiteral( "databaseName" ) ).isValid() )
5665         baseName = parts.value( QStringLiteral( "databaseName" ) ).toString();
5666       else
5667         baseName = uri;
5668     }
5669     else //directory //protocol
5670     {
5671       baseName = QgsProviderUtils::suggestLayerNameFromFilePath( uri );
5672     }
5673 
5674     if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
5675     {
5676       baseName = QgsMapLayer::formatLayerName( baseName );
5677     }
5678 
5679     QgsDebugMsgLevel( "completeBaseName: " + baseName, 2 );
5680     const bool isVsiCurl { uri.startsWith( QLatin1String( "/vsicurl" ), Qt::CaseInsensitive ) };
5681     const auto scheme { QUrl( uri ).scheme() };
5682     const bool isRemoteUrl { scheme.startsWith( QLatin1String( "http" ) ) || scheme == QLatin1String( "ftp" ) };
5683 
5684     std::unique_ptr< QgsTemporaryCursorOverride > cursorOverride;
5685     if ( isVsiCurl || isRemoteUrl )
5686     {
5687       cursorOverride = std::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
5688       visibleMessageBar()->pushInfo( tr( "Remote layer" ), tr( "loading %1, please wait …" ).arg( uri ) );
5689       qApp->processEvents();
5690     }
5691 
5692     QList< QgsProviderSublayerDetails > sublayers = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) )->querySublayers( uri, Qgis::SublayerQueryFlag::IncludeSystemTables );
5693     // filter out non-vector sublayers
5694     sublayers.erase( std::remove_if( sublayers.begin(), sublayers.end(), []( const QgsProviderSublayerDetails & sublayer )
5695     {
5696       return sublayer.type() != QgsMapLayerType::VectorLayer;
5697     } ), sublayers.end() );
5698 
5699     cursorOverride.reset();
5700 
5701     const QVariantMap uriParts = QgsProviderRegistry::instance()->decodeUri( QStringLiteral( "ogr" ), uri );
5702     const QString path = uriParts.value( QStringLiteral( "path" ) ).toString();
5703 
5704     if ( !sublayers.empty() )
5705     {
5706       userAskedToAddLayers = true;
5707 
5708       const bool detailsAreIncomplete = QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers, QgsProviderUtils::SublayerCompletenessFlag::IgnoreUnknownFeatureCount );
5709       const bool singleSublayerOnly = sublayers.size() == 1;
5710       QString groupName;
5711 
5712       if ( !singleSublayerOnly || detailsAreIncomplete )
5713       {
5714         // ask user for sublayers (unless user settings dictate otherwise!)
5715         switch ( shouldAskUserForSublayers( sublayers ) )
5716         {
5717           case SublayerHandling::AskUser:
5718           {
5719             // prompt user for sublayers
5720             QgsProviderSublayersDialog dlg( uri, path, sublayers, {QgsMapLayerType::VectorLayer}, this );
5721 
5722             if ( dlg.exec() )
5723               sublayers = dlg.selectedLayers();
5724             else
5725               sublayers.clear(); // dialog was canceled, so don't add any sublayers
5726             groupName = dlg.groupName();
5727             break;
5728           }
5729 
5730           case SublayerHandling::LoadAll:
5731           {
5732             if ( detailsAreIncomplete )
5733             {
5734               // requery sublayers, resolving geometry types
5735               sublayers = QgsProviderRegistry::instance()->querySublayers( uri, Qgis::SublayerQueryFlag::ResolveGeometryType );
5736               // filter out non-vector sublayers
5737               sublayers.erase( std::remove_if( sublayers.begin(), sublayers.end(), []( const QgsProviderSublayerDetails & sublayer )
5738               {
5739                 return sublayer.type() != QgsMapLayerType::VectorLayer;
5740               } ), sublayers.end() );
5741             }
5742             break;
5743           }
5744 
5745           case SublayerHandling::AbortLoading:
5746             sublayers.clear(); // don't add any sublayers
5747             break;
5748         };
5749       }
5750       else if ( detailsAreIncomplete )
5751       {
5752         // requery sublayers, resolving geometry types
5753         sublayers = QgsProviderRegistry::instance()->querySublayers( uri, Qgis::SublayerQueryFlag::ResolveGeometryType );
5754         // filter out non-vector sublayers
5755         sublayers.erase( std::remove_if( sublayers.begin(), sublayers.end(), []( const QgsProviderSublayerDetails & sublayer )
5756         {
5757           return sublayer.type() != QgsMapLayerType::VectorLayer;
5758         } ), sublayers.end() );
5759       }
5760 
5761       // now add sublayers
5762       if ( !sublayers.empty() )
5763       {
5764         addedLayers << addSublayers( sublayers, baseName, groupName );
5765       }
5766 
5767     }
5768     else
5769     {
5770       QString msg = tr( "%1 is not a valid or recognized data source." ).arg( uri );
5771       // If the failed layer was a vsicurl type, give the user a chance to try the normal download.
5772       if ( isVsiCurl &&
5773            QMessageBox::question( this, tr( "Invalid Data Source" ),
5774                                   tr( "Download with \"Protocol\" source type has failed, do you want to try the \"File\" source type?" ) ) == QMessageBox::Yes )
5775       {
5776         QString fileUri = uri;
5777         fileUri.replace( QLatin1String( "/vsicurl/" ), " " );
5778         return addVectorLayersPrivate( QStringList() << fileUri, enc, dataSourceType, guiWarning );
5779       }
5780       else if ( guiWarning )
5781       {
5782         visibleMessageBar()->pushMessage( tr( "Invalid Data Source" ), msg, Qgis::MessageLevel::Critical );
5783       }
5784     }
5785   }
5786 
5787   // make sure at least one layer was successfully added
5788   if ( layersToAdd.isEmpty() )
5789   {
5790     // we also return true if we asked the user for sublayers, but they choose none. In this case nothing
5791     // went wrong, so we shouldn't return false and cause GUI warnings to appear
5792     return userAskedToAddLayers || !addedLayers.isEmpty();
5793   }
5794 
5795   // Register this layer with the layers registry
5796   QgsProject::instance()->addMapLayers( layersToAdd );
5797   for ( QgsMapLayer *l : std::as_const( layersToAdd ) )
5798   {
5799     if ( !enc.isEmpty() )
5800     {
5801       if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( l ) )
5802         vl->setProviderEncoding( enc );
5803     }
5804 
5805     askUserForDatumTransform( l->crs(), QgsProject::instance()->crs(), l );
5806     postProcessAddedLayer( l );
5807   }
5808   activateDeactivateLayerRelatedActions( activeLayer() );
5809 
5810   return true;
5811 }
5812 
addMeshLayer(const QString & url,const QString & baseName,const QString & providerKey)5813 QgsMeshLayer *QgisApp::addMeshLayer( const QString &url, const QString &baseName, const QString &providerKey )
5814 {
5815   return addLayerPrivate< QgsMeshLayer >( QgsMapLayerType::MeshLayer, url, baseName, providerKey, true );
5816 }
5817 
addSublayers(const QList<QgsProviderSublayerDetails> & layers,const QString & baseName,const QString & groupName)5818 QList< QgsMapLayer * > QgisApp::addSublayers( const QList<QgsProviderSublayerDetails> &layers, const QString &baseName, const QString &groupName )
5819 {
5820   QgsLayerTreeGroup *group = nullptr;
5821   if ( !groupName.isEmpty() )
5822   {
5823     group = QgsProject::instance()->layerTreeRoot()->insertGroup( 0, groupName );
5824   }
5825 
5826   QgsSettings settings;
5827   const bool formatLayerNames = settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool();
5828 
5829   // if we aren't adding to a group, we need to add the layers in reverse order so that they maintain the correct
5830   // order in the layer tree!
5831   QList<QgsProviderSublayerDetails> sortedLayers = layers;
5832   if ( groupName.isEmpty() )
5833   {
5834     std::reverse( sortedLayers.begin(), sortedLayers.end() );
5835   }
5836 
5837   QList< QgsMapLayer * > result;
5838   result.reserve( sortedLayers.size() );
5839 
5840   for ( const QgsProviderSublayerDetails &sublayer : std::as_const( sortedLayers ) )
5841   {
5842     QgsProviderSublayerDetails::LayerOptions options( QgsProject::instance()->transformContext() );
5843     options.loadDefaultStyle = false;
5844 
5845     std::unique_ptr<QgsMapLayer> layer( sublayer.toLayer( options ) );
5846     if ( !layer )
5847       continue;
5848 
5849     QgsMapLayer *ml = layer.get();
5850     // if we aren't adding to a group, then we're iterating the layers in the reverse order
5851     // so account for that in the returned list of layers
5852     if ( groupName.isEmpty() )
5853       result.insert( 0, ml );
5854     else
5855       result << ml;
5856 
5857     QString layerName = layer->name();
5858     if ( formatLayerNames )
5859     {
5860       layerName = QgsMapLayer::formatLayerName( layerName );
5861     }
5862 
5863     // if user has opted to add sublayers to a group, then we don't need to include the
5864     // filename in the layer's name, because the group is already titled with the filename.
5865     // But otherwise, we DO include the file name so that users can differentiate the source
5866     // when multiple layers are loaded from a GPX file or similar (refs https://github.com/qgis/QGIS/issues/37551)
5867     if ( group )
5868     {
5869       if ( !layerName.isEmpty() )
5870         layer->setName( layerName );
5871       else if ( !baseName.isEmpty() )
5872         layer->setName( baseName );
5873       QgsProject::instance()->addMapLayer( layer.release(), false );
5874       group->addLayer( ml );
5875     }
5876     else
5877     {
5878       if ( layerName != baseName && !layerName.isEmpty() && !baseName.isEmpty() )
5879         layer->setName( QStringLiteral( "%1 — %2" ).arg( baseName, layerName ) );
5880       else if ( !layerName.isEmpty() )
5881         layer->setName( layerName );
5882       else if ( !baseName.isEmpty() )
5883         layer->setName( baseName );
5884       QgsProject::instance()->addMapLayer( layer.release() );
5885     }
5886 
5887     askUserForDatumTransform( ml->crs(), QgsProject::instance()->crs(), ml );
5888     postProcessAddedLayer( ml );
5889   }
5890 
5891   if ( group )
5892   {
5893     // Respect if user don't want the new group of layers visible.
5894     QgsSettings settings;
5895     const bool newLayersVisible = settings.value( QStringLiteral( "/qgis/new_layers_visible" ), true ).toBool();
5896     if ( !newLayersVisible )
5897       group->setItemVisibilityCheckedRecursive( newLayersVisible );
5898   }
5899 
5900   return result;
5901 }
5902 
postProcessAddedLayer(QgsMapLayer * layer)5903 void QgisApp::postProcessAddedLayer( QgsMapLayer *layer )
5904 {
5905   switch ( layer->type() )
5906   {
5907     case QgsMapLayerType::VectorLayer:
5908     case QgsMapLayerType::RasterLayer:
5909     {
5910       bool ok = false;
5911       layer->loadDefaultStyle( ok );
5912       layer->loadDefaultMetadata( ok );
5913       break;
5914     }
5915 
5916     case QgsMapLayerType::PluginLayer:
5917       break;
5918 
5919     case QgsMapLayerType::MeshLayer:
5920     {
5921       QgsMeshLayer *meshLayer = qobject_cast< QgsMeshLayer *>( layer );
5922       QDateTime referenceTime = QgsProject::instance()->timeSettings()->temporalRange().begin();
5923       if ( !referenceTime.isValid() ) // If project reference time is invalid, use current date
5924         referenceTime = QDateTime( QDate::currentDate(), QTime( 0, 0, 0 ), Qt::UTC );
5925 
5926       if ( meshLayer->dataProvider() && !qobject_cast< QgsMeshLayerTemporalProperties * >( meshLayer->temporalProperties() )->referenceTime().isValid() )
5927         qobject_cast< QgsMeshLayerTemporalProperties * >( meshLayer->temporalProperties() )->setReferenceTime( referenceTime, meshLayer->dataProvider()->temporalCapabilities() );
5928 
5929       bool ok = false;
5930       meshLayer->loadDefaultStyle( ok );
5931       meshLayer->loadDefaultMetadata( ok );
5932       break;
5933     }
5934 
5935     case QgsMapLayerType::VectorTileLayer:
5936     {
5937       bool ok = false;
5938       QString error = layer->loadDefaultStyle( ok );
5939       if ( !ok )
5940         visibleMessageBar()->pushMessage( tr( "Error loading style" ), error, Qgis::MessageLevel::Warning );
5941       error = layer->loadDefaultMetadata( ok );
5942       if ( !ok )
5943         visibleMessageBar()->pushMessage( tr( "Error loading layer metadata" ), error, Qgis::MessageLevel::Warning );
5944 
5945       break;
5946     }
5947 
5948     case QgsMapLayerType::AnnotationLayer:
5949       break;
5950 
5951     case QgsMapLayerType::PointCloudLayer:
5952     {
5953       bool ok = false;
5954       layer->loadDefaultStyle( ok );
5955       layer->loadDefaultMetadata( ok );
5956 
5957 #ifdef HAVE_3D
5958       if ( !layer->renderer3D() )
5959       {
5960         QgsPointCloudLayer *pcLayer = qobject_cast< QgsPointCloudLayer * >( layer );
5961         // for point clouds we default to a 3d renderer. it just makes sense :)
5962         std::unique_ptr< QgsPointCloudLayer3DRenderer > renderer3D = Qgs3DAppUtils::convert2dPointCloudRendererTo3d( pcLayer->renderer() );
5963         if ( renderer3D )
5964           layer->setRenderer3D( renderer3D.release() );
5965         else
5966         {
5967           // maybe waiting on an index...
5968           if ( pcLayer->dataProvider()->indexingState() != QgsPointCloudDataProvider::Indexed )
5969           {
5970             QPointer< QgsPointCloudLayer > layerPointer( pcLayer );
5971             connect( pcLayer->dataProvider(), &QgsPointCloudDataProvider::indexGenerationStateChanged, this, [layerPointer]( QgsPointCloudDataProvider::PointCloudIndexGenerationState state )
5972             {
5973               if ( !layerPointer || state != QgsPointCloudDataProvider::Indexed )
5974                 return;
5975 
5976               std::unique_ptr< QgsPointCloudLayer3DRenderer > renderer3D = Qgs3DAppUtils::convert2dPointCloudRendererTo3d( layerPointer->renderer() );
5977               if ( renderer3D )
5978                 layerPointer->setRenderer3D( renderer3D.release() );
5979             } );
5980           }
5981         }
5982       }
5983 #endif
5984       break;
5985     }
5986   }
5987 }
5988 
addVectorTileLayer(const QString & url,const QString & baseName)5989 QgsVectorTileLayer *QgisApp::addVectorTileLayer( const QString &url, const QString &baseName )
5990 {
5991   return addVectorTileLayerPrivate( url, baseName );
5992 }
5993 
addPointCloudLayer(const QString & url,const QString & baseName,const QString & providerKey)5994 QgsPointCloudLayer *QgisApp::addPointCloudLayer( const QString &url, const QString &baseName, const QString &providerKey )
5995 {
5996   return addPointCloudLayerPrivate( url, baseName, providerKey );
5997 }
5998 
addVectorTileLayerPrivate(const QString & url,const QString & baseName,const bool guiWarning)5999 QgsVectorTileLayer *QgisApp::addVectorTileLayerPrivate( const QString &url, const QString &baseName, const bool guiWarning )
6000 {
6001   QgsCanvasRefreshBlocker refreshBlocker;
6002   QgsSettings settings;
6003 
6004   QString base( baseName );
6005 
6006   if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
6007   {
6008     base = QgsMapLayer::formatLayerName( base );
6009   }
6010 
6011   QgsDebugMsgLevel( "completeBaseName: " + base, 2 );
6012 
6013   // create the layer
6014   const QgsVectorTileLayer::LayerOptions options( QgsProject::instance()->transformContext() );
6015   std::unique_ptr<QgsVectorTileLayer> layer( new QgsVectorTileLayer( url, base, options ) );
6016 
6017   if ( !layer || !layer->isValid() )
6018   {
6019     if ( guiWarning )
6020     {
6021       QString msg = tr( "%1 is not a valid or recognized data source." ).arg( url );
6022       visibleMessageBar()->pushMessage( tr( "Invalid Data Source" ), msg, Qgis::MessageLevel::Critical );
6023     }
6024 
6025     // since the layer is bad, stomp on it
6026     return nullptr;
6027   }
6028 
6029   postProcessAddedLayer( layer.get() );
6030 
6031   QgsProject::instance()->addMapLayer( layer.get() );
6032   activateDeactivateLayerRelatedActions( activeLayer() );
6033 
6034   return layer.release();
6035 }
6036 
addPointCloudLayerPrivate(const QString & uri,const QString & baseName,const QString & providerKey,bool guiWarning)6037 QgsPointCloudLayer *QgisApp::addPointCloudLayerPrivate( const QString &uri, const QString &baseName, const QString &providerKey, bool guiWarning )
6038 {
6039   QgsCanvasRefreshBlocker refreshBlocker;
6040   QgsSettings settings;
6041 
6042   QString base( baseName );
6043 
6044   if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
6045   {
6046     base = QgsMapLayer::formatLayerName( base );
6047   }
6048 
6049   QgsDebugMsgLevel( "completeBaseName: " + base, 2 );
6050 
6051   // create the layer
6052   std::unique_ptr<QgsPointCloudLayer> layer( new QgsPointCloudLayer( uri, base, providerKey ) );
6053 
6054   if ( !layer || !layer->isValid() )
6055   {
6056     if ( guiWarning )
6057     {
6058       QString msg = tr( "%1 is not a valid or recognized data source." ).arg( uri );
6059       visibleMessageBar()->pushMessage( tr( "Invalid Data Source" ), msg, Qgis::MessageLevel::Critical );
6060     }
6061 
6062     // since the layer is bad, stomp on it
6063     return nullptr;
6064   }
6065 
6066   postProcessAddedLayer( layer.get() );
6067 
6068 
6069   QgsProject::instance()->addMapLayer( layer.get() );
6070   activateDeactivateLayerRelatedActions( activeLayer() );
6071 
6072   return layer.release();
6073 }
6074 
askUserForZipItemLayers(const QString & path,const QList<QgsMapLayerType> & acceptableTypes)6075 bool QgisApp::askUserForZipItemLayers( const QString &path, const QList< QgsMapLayerType > &acceptableTypes )
6076 {
6077   // query sublayers
6078   QList< QgsProviderSublayerDetails > sublayers = QgsProviderRegistry::instance()->querySublayers( path, Qgis::SublayerQueryFlag::IncludeSystemTables );
6079 
6080   // filter out non-matching sublayers
6081   sublayers.erase( std::remove_if( sublayers.begin(), sublayers.end(), [acceptableTypes]( const QgsProviderSublayerDetails & sublayer )
6082   {
6083     return !acceptableTypes.empty() && !acceptableTypes.contains( sublayer.type() );
6084   } ), sublayers.end() );
6085 
6086   if ( sublayers.empty() )
6087     return false;
6088 
6089   const bool detailsAreIncomplete = QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers, QgsProviderUtils::SublayerCompletenessFlag::IgnoreUnknownFeatureCount );
6090   const bool singleSublayerOnly = sublayers.size() == 1;
6091   QString groupName;
6092 
6093   if ( !singleSublayerOnly || detailsAreIncomplete )
6094   {
6095     // ask user for sublayers (unless user settings dictate otherwise!)
6096     switch ( shouldAskUserForSublayers( sublayers ) )
6097     {
6098       case SublayerHandling::AskUser:
6099       {
6100         // prompt user for sublayers
6101         QgsProviderSublayersDialog dlg( path, path, sublayers, acceptableTypes, this );
6102 
6103         if ( dlg.exec() )
6104           sublayers = dlg.selectedLayers();
6105         else
6106           sublayers.clear(); // dialog was canceled, so don't add any sublayers
6107         groupName = dlg.groupName();
6108         break;
6109       }
6110 
6111       case SublayerHandling::LoadAll:
6112       {
6113         if ( detailsAreIncomplete )
6114         {
6115           // requery sublayers, resolving geometry types
6116           sublayers = QgsProviderRegistry::instance()->querySublayers( path, Qgis::SublayerQueryFlag::ResolveGeometryType );
6117           sublayers.erase( std::remove_if( sublayers.begin(), sublayers.end(), [acceptableTypes]( const QgsProviderSublayerDetails & sublayer )
6118           {
6119             return !acceptableTypes.empty() && !acceptableTypes.contains( sublayer.type() );
6120           } ), sublayers.end() );
6121         }
6122         break;
6123       }
6124 
6125       case SublayerHandling::AbortLoading:
6126         sublayers.clear(); // don't add any sublayers
6127         break;
6128     };
6129   }
6130   else if ( detailsAreIncomplete )
6131   {
6132     // requery sublayers, resolving geometry types
6133     sublayers = QgsProviderRegistry::instance()->querySublayers( path, Qgis::SublayerQueryFlag::ResolveGeometryType );
6134     sublayers.erase( std::remove_if( sublayers.begin(), sublayers.end(), [acceptableTypes]( const QgsProviderSublayerDetails & sublayer )
6135     {
6136       return !acceptableTypes.empty() && !acceptableTypes.contains( sublayer.type() );
6137     } ), sublayers.end() );
6138   }
6139 
6140   // now add sublayers
6141   if ( !sublayers.empty() )
6142   {
6143     QgsCanvasRefreshBlocker refreshBlocker;
6144     QgsSettings settings;
6145 
6146     QString base = QgsProviderUtils::suggestLayerNameFromFilePath( path );
6147     if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
6148     {
6149       base = QgsMapLayer::formatLayerName( base );
6150     }
6151 
6152     addSublayers( sublayers, base, groupName );
6153     activateDeactivateLayerRelatedActions( activeLayer() );
6154   }
6155 
6156   return true;
6157 }
6158 
shouldAskUserForSublayers(const QList<QgsProviderSublayerDetails> & layers,bool hasNonLayerItems) const6159 QgisApp::SublayerHandling QgisApp::shouldAskUserForSublayers( const QList<QgsProviderSublayerDetails> &layers, bool hasNonLayerItems ) const
6160 {
6161   if ( hasNonLayerItems )
6162     return SublayerHandling::AskUser;
6163 
6164   QgsSettings settings;
6165   const Qgis::SublayerPromptMode promptLayers = settings.enumValue( QStringLiteral( "qgis/promptForSublayers" ), Qgis::SublayerPromptMode::AlwaysAsk );
6166 
6167   switch ( promptLayers )
6168   {
6169     case Qgis::SublayerPromptMode::AlwaysAsk:
6170       return SublayerHandling::AskUser;
6171 
6172     case Qgis::SublayerPromptMode::AskExcludingRasterBands:
6173     {
6174       // if any non-raster layers are found, we ask the user. Otherwise we load all
6175       for ( const QgsProviderSublayerDetails &sublayer : layers )
6176       {
6177         if ( sublayer.type() != QgsMapLayerType::RasterLayer )
6178           return SublayerHandling::AskUser;
6179       }
6180       return SublayerHandling::LoadAll;
6181     }
6182 
6183     case Qgis::SublayerPromptMode::NeverAskSkip:
6184       return SublayerHandling::AbortLoading;
6185 
6186     case Qgis::SublayerPromptMode::NeverAskLoadAll:
6187       return SublayerHandling::LoadAll;
6188   }
6189 
6190   return SublayerHandling::AskUser;
6191 }
6192 
addDatabaseLayers(QStringList const & layerPathList,QString const & providerKey)6193 void QgisApp::addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey )
6194 {
6195   QList<QgsMapLayer *> myList;
6196 
6197   if ( layerPathList.empty() )
6198   {
6199     // no layers to add so bail out, but
6200     // allow mMapCanvas to handle events
6201     // first
6202     return;
6203   }
6204 
6205   QgsCanvasRefreshBlocker refreshBlocker;
6206 
6207   QApplication::setOverrideCursor( Qt::WaitCursor );
6208 
6209   const auto constLayerPathList = layerPathList;
6210   for ( const QString &layerPath : constLayerPathList )
6211   {
6212     // create the layer
6213     QgsDataSourceUri uri( layerPath );
6214 
6215     QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
6216     options.loadDefaultStyle = false;
6217     QgsVectorLayer *layer = new QgsVectorLayer( uri.uri( false ), uri.table(), providerKey, options );
6218     Q_CHECK_PTR( layer );
6219 
6220     if ( ! layer )
6221     {
6222       QApplication::restoreOverrideCursor();
6223 
6224       // XXX insert meaningful whine to the user here
6225       return;
6226     }
6227 
6228     if ( layer->isValid() )
6229     {
6230       // add to list of layers to register
6231       //with the central layers registry
6232       myList << layer;
6233     }
6234     else
6235     {
6236       QgsMessageLog::logMessage( tr( "%1 is an invalid layer - not loaded" ).arg( layerPath ) );
6237       QLabel *msgLabel = new QLabel( tr( "%1 is an invalid layer and cannot be loaded. Please check the <a href=\"#messageLog\">message log</a> for further info." ).arg( layerPath ), messageBar() );
6238       msgLabel->setWordWrap( true );
6239       connect( msgLabel, &QLabel::linkActivated, mLogDock, &QWidget::show );
6240       QgsMessageBarItem *item = new QgsMessageBarItem( msgLabel, Qgis::MessageLevel::Warning );
6241       messageBar()->pushItem( item );
6242       delete layer;
6243     }
6244     //qWarning("incrementing iterator");
6245   }
6246 
6247   QgsProject::instance()->addMapLayers( myList );
6248 
6249   // load default style after adding to process readCustomSymbology signals
6250   const auto constMyList = myList;
6251   for ( QgsMapLayer *l : constMyList )
6252   {
6253     bool ok;
6254     l->loadDefaultStyle( ok );
6255     l->loadDefaultMetadata( ok );
6256   }
6257 
6258   QApplication::restoreOverrideCursor();
6259 }
6260 
addVirtualLayer()6261 void QgisApp::addVirtualLayer()
6262 {
6263   // show the virtual layer dialog
6264   QDialog *dts = dynamic_cast<QDialog *>( QgsGui::sourceSelectProviderRegistry()->createSelectionWidget( QStringLiteral( "virtual" ), this, Qt::Widget, QgsProviderRegistry::WidgetMode::Embedded ) );
6265   if ( !dts )
6266   {
6267     QMessageBox::warning( this, tr( "Add Virtual Layer" ), tr( "Cannot get virtual layer select dialog from provider." ) );
6268     return;
6269   }
6270   connect( dts, SIGNAL( addVectorLayer( QString, QString, QString ) ),
6271            this, SLOT( onVirtualLayerAdded( QString, QString ) ) );
6272   connect( dts, SIGNAL( replaceVectorLayer( QString, QString, QString, QString ) ),
6273            this, SLOT( replaceSelectedVectorLayer( QString, QString, QString, QString ) ) );
6274   dts->exec();
6275   delete dts;
6276 }
6277 
addSelectedVectorLayer(const QString & uri,const QString & layerName,const QString & provider)6278 void QgisApp::addSelectedVectorLayer( const QString &uri, const QString &layerName, const QString &provider )
6279 {
6280   addVectorLayer( uri, layerName, provider );
6281 }
6282 
replaceSelectedVectorLayer(const QString & oldId,const QString & uri,const QString & layerName,const QString & provider)6283 void QgisApp::replaceSelectedVectorLayer( const QString &oldId, const QString &uri, const QString &layerName, const QString &provider )
6284 {
6285   QgsMapLayer *old = QgsProject::instance()->mapLayer( oldId );
6286   if ( !old )
6287     return;
6288   QgsVectorLayer *oldLayer = static_cast<QgsVectorLayer *>( old );
6289   const QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
6290   QgsVectorLayer *newLayer = new QgsVectorLayer( uri, layerName, provider, options );
6291   if ( !newLayer || !newLayer->isValid() )
6292     return;
6293 
6294   QgsProject::instance()->addMapLayer( newLayer, /*addToLegend*/ false, /*takeOwnership*/ true );
6295   duplicateVectorStyle( oldLayer, newLayer );
6296 
6297   // insert the new layer just below the old one
6298   QgsLayerTreeUtils::insertLayerBelow( QgsProject::instance()->layerTreeRoot(), oldLayer, newLayer );
6299   // and remove the old layer
6300   QgsProject::instance()->removeMapLayer( oldLayer );
6301 } // QgisApp:replaceSelectedVectorLayer
6302 
fileExit()6303 void QgisApp::fileExit()
6304 {
6305   if ( QgsApplication::taskManager()->countActiveTasks() > 0 )
6306   {
6307     QStringList tasks;
6308     const QList< QgsTask * > activeTasks = QgsApplication::taskManager()->activeTasks();
6309     for ( QgsTask *task : activeTasks )
6310     {
6311       if ( task->flags() & QgsTask::CancelWithoutPrompt )
6312         continue;
6313 
6314       tasks << tr( " • %1" ).arg( task->description() );
6315     }
6316 
6317     // prompt if any tasks which require user confirmation remain, otherwise just cancel them directly and continue with shutdown.
6318     if ( tasks.empty() )
6319     {
6320       // all tasks can be silently terminated without warning
6321       QgsApplication::taskManager()->cancelAll();
6322     }
6323     else
6324     {
6325       if ( QMessageBox::question( this, tr( "Active Tasks" ),
6326                                   tr( "The following tasks are currently running in the background:\n\n%1\n\nDo you want to try canceling these active tasks?" ).arg( tasks.join( QLatin1Char( '\n' ) ) ),
6327                                   QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes )
6328       {
6329         QgsApplication::taskManager()->cancelAll();
6330       }
6331       return;
6332     }
6333   }
6334 
6335   QgsCanvasRefreshBlocker refreshBlocker;
6336   if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() && checkExitBlockers() )
6337   {
6338     closeProject();
6339     userProfileManager()->setDefaultFromActive();
6340 
6341     // shouldn't be needed, but from this stage on, we don't want/need ANY map canvas refreshes to take place
6342     mFreezeCount = 1000000;
6343     qApp->exit( 0 );
6344   }
6345 }
6346 
6347 
fileNew()6348 bool QgisApp::fileNew()
6349 {
6350   return fileNew( true ); // prompts whether to save project
6351 } // fileNew()
6352 
6353 
fileNewBlank()6354 bool QgisApp::fileNewBlank()
6355 {
6356   return fileNew( true, true );
6357 }
6358 
fileClose()6359 void QgisApp::fileClose()
6360 {
6361   if ( fileNewBlank() )
6362     mCentralContainer->setCurrentIndex( 1 );
6363 }
6364 
6365 
6366 //as file new but accepts flags to indicate whether we should prompt to save
fileNew(bool promptToSaveFlag,bool forceBlank)6367 bool QgisApp::fileNew( bool promptToSaveFlag, bool forceBlank )
6368 {
6369   if ( checkTasksDependOnProject() )
6370     return false;
6371 
6372   if ( promptToSaveFlag )
6373   {
6374     if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() )
6375     {
6376       return false; //cancel pressed
6377     }
6378   }
6379 
6380   mProjectLastModified = QDateTime();
6381 
6382   QgsSettings settings;
6383 
6384   MAYBE_UNUSED QgsProjectDirtyBlocker dirtyBlocker( QgsProject::instance() );
6385   QgsCanvasRefreshBlocker refreshBlocker;
6386   closeProject();
6387 
6388   QgsProject *prj = QgsProject::instance();
6389   prj->layerTreeRegistryBridge()->setNewLayersVisible( settings.value( QStringLiteral( "qgis/new_layers_visible" ), true ).toBool() );
6390 
6391   //set the canvas to the default project background color
6392   mOverviewCanvas->setBackgroundColor( prj->backgroundColor() );
6393   applyProjectSettingsToCanvas( mMapCanvas );
6394 
6395   prj->setDirty( false );
6396 
6397   setTitleBarText_( *this );
6398 
6399   // emit signal so listeners know we have a new project
6400   emit newProject();
6401 
6402   mMapCanvas->clearExtentHistory();
6403   mMapCanvas->setRotation( 0.0 );
6404   mScaleWidget->updateScales();
6405 
6406   // set project CRS
6407   const QgsCoordinateReferenceSystem srs = QgsCoordinateReferenceSystem( settings.value( QStringLiteral( "/projections/defaultProjectCrs" ), geoEpsgCrsAuthId(), QgsSettings::App ).toString() );
6408   // write the projections _proj string_ to project settings
6409   const bool planimetric = settings.value( QStringLiteral( "measure/planimetric" ), true, QgsSettings::Core ).toBool();
6410   prj->setCrs( srs, !planimetric ); // If the default ellipsoid is not planimetric, set it from the default crs
6411   if ( planimetric )
6412     prj->setEllipsoid( geoNone() );
6413 
6414   /* New Empty Project Created
6415       (before attempting to load custom project templates/filepaths) */
6416 
6417   // load default template
6418   /* NOTE: don't open default template on launch until after initialization,
6419            in case a project was defined via command line */
6420 
6421   // don't open template if last auto-opening of a project failed
6422   if ( ! forceBlank )
6423   {
6424     forceBlank = ! settings.value( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( true ) ).toBool();
6425   }
6426 
6427   if ( ! forceBlank && settings.value( QStringLiteral( "qgis/newProjectDefault" ), QVariant( false ) ).toBool() )
6428   {
6429     fileNewFromDefaultTemplate();
6430   }
6431 
6432   // set the initial map tool
6433   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::Pan ) );
6434   mNonEditMapTool = mMapTools->mapTool( QgsAppMapTools::Pan );  // signals are not yet setup to catch this
6435 
6436   prj->setDirty( false );
6437   return true;
6438 }
6439 
fileNewFromTemplate(const QString & fileName)6440 bool QgisApp::fileNewFromTemplate( const QString &fileName )
6441 {
6442   if ( checkTasksDependOnProject() )
6443     return false;
6444 
6445   if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() || !saveDirty() )
6446   {
6447     return false; //cancel pressed
6448   }
6449 
6450   MAYBE_UNUSED QgsProjectDirtyBlocker dirtyBlocker( QgsProject::instance() );
6451   QgsDebugMsgLevel( QStringLiteral( "loading project template: %1" ).arg( fileName ), 2 );
6452   if ( addProject( fileName ) )
6453   {
6454     // set null filename so we don't override the template
6455     QgsProject::instance()->setFileName( QString() );
6456     return true;
6457   }
6458   return false;
6459 }
6460 
fileNewFromDefaultTemplate()6461 void QgisApp::fileNewFromDefaultTemplate()
6462 {
6463   QString projectTemplate = QgsApplication::qgisSettingsDirPath() + QStringLiteral( "project_default.qgs" );
6464   QString msgTxt;
6465   if ( !projectTemplate.isEmpty() && QFile::exists( projectTemplate ) )
6466   {
6467     if ( fileNewFromTemplate( projectTemplate ) )
6468     {
6469       return;
6470     }
6471     msgTxt = tr( "Default failed to open: %1" );
6472   }
6473   else
6474   {
6475     msgTxt = tr( "Default not found: %1" );
6476   }
6477   visibleMessageBar()->pushMessage( tr( "Open Template Project" ),
6478                                     msgTxt.arg( projectTemplate ),
6479                                     Qgis::MessageLevel::Warning );
6480 }
6481 
fileOpenAfterLaunch()6482 void QgisApp::fileOpenAfterLaunch()
6483 {
6484   // TODO: move auto-open project options to enums
6485 
6486   // check if a project is already loaded via command line or filesystem
6487   if ( !QgsProject::instance()->fileName().isNull() )
6488   {
6489     return;
6490   }
6491 
6492   // check if a data source is already loaded via command line or filesystem
6493   // empty project with layer loaded, but may not trigger a dirty project at this point
6494   if ( QgsProject::instance() && QgsProject::instance()->count() > 0 )
6495   {
6496     return;
6497   }
6498 
6499   // fileNewBlank() has already been called in QgisApp constructor
6500   // loaded project is either a new blank one, or one from command line/filesystem
6501   QgsSettings settings;
6502   QString autoOpenMsgTitle = tr( "Auto-open Project" );
6503 
6504   // get path of project file to open, or was attempted
6505   QString projPath;
6506 
6507   if ( mProjOpen == 0 ) // welcome page
6508   {
6509     connect( this, &QgisApp::newProject, this, &QgisApp::showMapCanvas );
6510     connect( this, &QgisApp::projectRead, this, &QgisApp::showMapCanvas );
6511     return;
6512   }
6513   if ( mProjOpen == 1 && !mRecentProjects.isEmpty() ) // most recent project
6514   {
6515     projPath = mRecentProjects.at( 0 ).path;
6516   }
6517   if ( mProjOpen == 2 ) // specific project
6518   {
6519     projPath = settings.value( QStringLiteral( "qgis/projOpenAtLaunchPath" ) ).toString();
6520   }
6521 
6522   // whether last auto-opening of a project failed
6523   bool projOpenedOK = settings.value( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( true ) ).toBool();
6524 
6525   // notify user if last attempt at auto-opening a project failed
6526 
6527   /* NOTE: Notification will not show if last auto-opened project failed but
6528       next project opened is from command line (minor issue) */
6529 
6530   /* TODO: Keep projOpenedOKAtLaunch from being reset to true after
6531       reading command line project (which happens before initialization signal) */
6532   if ( !projOpenedOK )
6533   {
6534     // only show the following 'auto-open project failed' message once, at launch
6535     settings.setValue( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( true ) );
6536 
6537     // set auto-open project back to 'New' to avoid re-opening bad project
6538     settings.setValue( QStringLiteral( "qgis/projOpenAtLaunch" ), QVariant( 0 ) );
6539 
6540     visibleMessageBar()->pushMessage( autoOpenMsgTitle,
6541                                       tr( "Failed to open: %1" ).arg( projPath ),
6542                                       Qgis::MessageLevel::Critical );
6543     return;
6544   }
6545 
6546   if ( mProjOpen == 3 ) // new project
6547   {
6548     // open default template, if defined
6549     if ( settings.value( QStringLiteral( "qgis/newProjectDefault" ), QVariant( false ) ).toBool() )
6550     {
6551       fileNewFromDefaultTemplate();
6552     }
6553     return;
6554   }
6555 
6556   if ( projPath.isEmpty() ) // projPath required from here
6557   {
6558     return;
6559   }
6560 
6561   // Is this a storage based project?
6562   const bool projectIsFromStorage { QgsApplication::instance()->projectStorageRegistry()->projectStorageFromUri( projPath ) != nullptr };
6563 
6564   if ( !projectIsFromStorage &&
6565        !projPath.endsWith( QLatin1String( ".qgs" ), Qt::CaseInsensitive ) &&
6566        !projPath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
6567   {
6568     visibleMessageBar()->pushMessage( autoOpenMsgTitle,
6569                                       tr( "Not valid project file: %1" ).arg( projPath ),
6570                                       Qgis::MessageLevel::Warning );
6571     return;
6572   }
6573 
6574   if ( projectIsFromStorage || QFile::exists( projPath ) )
6575   {
6576     // set flag to check on next app launch if the following project opened OK
6577     settings.setValue( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( false ) );
6578 
6579     if ( !addProject( projPath ) )
6580     {
6581       visibleMessageBar()->pushMessage( autoOpenMsgTitle,
6582                                         tr( "Project failed to open: %1" ).arg( projPath ),
6583                                         Qgis::MessageLevel::Warning );
6584     }
6585 
6586     if ( projPath.endsWith( QLatin1String( "project_default.qgs" ) ) )
6587     {
6588       visibleMessageBar()->pushMessage( autoOpenMsgTitle,
6589                                         tr( "Default template has been reopened: %1" ).arg( projPath ),
6590                                         Qgis::MessageLevel::Info );
6591     }
6592   }
6593   else
6594   {
6595     visibleMessageBar()->pushMessage( autoOpenMsgTitle,
6596                                       tr( "File not found: %1" ).arg( projPath ),
6597                                       Qgis::MessageLevel::Warning );
6598   }
6599 }
6600 
fileOpenedOKAfterLaunch()6601 void QgisApp::fileOpenedOKAfterLaunch()
6602 {
6603   QgsSettings settings;
6604   settings.setValue( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( true ) );
6605 }
6606 
fileNewFromTemplateAction(QAction * qAction)6607 void QgisApp::fileNewFromTemplateAction( QAction *qAction )
6608 {
6609   if ( ! qAction )
6610     return;
6611 
6612   if ( qAction->text() == tr( "< Blank >" ) )
6613   {
6614     fileNewBlank();
6615   }
6616   else
6617   {
6618     QgsSettings settings;
6619     QString templateDirName = settings.value( QStringLiteral( "qgis/projectTemplateDir" ),
6620                               QString( QgsApplication::qgisSettingsDirPath() + "project_templates" ) ).toString();
6621     fileNewFromTemplate( templateDirName + QDir::separator() + qAction->text() );
6622   }
6623 }
6624 
6625 
newVectorLayer()6626 void QgisApp::newVectorLayer()
6627 {
6628   QString enc;
6629   QString error;
6630   QString fileName = QgsNewVectorLayerDialog::execAndCreateLayer( error, this, QString(), &enc, QgsProject::instance()->defaultCrsForNewLayers() );
6631 
6632   if ( !fileName.isEmpty() )
6633   {
6634     //then add the layer to the view
6635     QStringList fileNames;
6636     fileNames.append( fileName );
6637     //todo: the last parameter will change accordingly to layer type
6638     addVectorLayers( fileNames, enc, QStringLiteral( "file" ) );
6639   }
6640   else if ( !error.isEmpty() )
6641   {
6642     QLabel *msgLabel = new QLabel( tr( "Layer creation failed: %1" ).arg( error ), messageBar() );
6643     msgLabel->setWordWrap( true );
6644     connect( msgLabel, &QLabel::linkActivated, mLogDock, &QWidget::show );
6645     QgsMessageBarItem *item = new QgsMessageBarItem( msgLabel, Qgis::MessageLevel::Critical );
6646     messageBar()->pushItem( item );
6647   }
6648 }
6649 
newMemoryLayer()6650 void QgisApp::newMemoryLayer()
6651 {
6652   QgsVectorLayer *newLayer = QgsNewMemoryLayerDialog::runAndCreateLayer( this, QgsProject::instance()->defaultCrsForNewLayers() );
6653 
6654   if ( newLayer )
6655   {
6656     //then add the layer to the view
6657     QList< QgsMapLayer * > layers;
6658     layers << newLayer;
6659 
6660     QgsProject::instance()->addMapLayers( layers );
6661     newLayer->startEditing();
6662   }
6663 }
6664 
6665 #ifdef HAVE_SPATIALITE
newSpatialiteLayer()6666 void QgisApp::newSpatialiteLayer()
6667 {
6668   QgsNewSpatialiteLayerDialog spatialiteDialog( this, QgsGuiUtils::ModalDialogFlags, QgsProject::instance()->defaultCrsForNewLayers() );
6669   spatialiteDialog.exec();
6670 }
6671 #endif
6672 
newGeoPackageLayer()6673 void QgisApp::newGeoPackageLayer()
6674 {
6675   QgsNewGeoPackageLayerDialog dialog( this );
6676   dialog.setCrs( QgsProject::instance()->defaultCrsForNewLayers() );
6677   dialog.exec();
6678 }
6679 
newMeshLayer()6680 void QgisApp::newMeshLayer()
6681 {
6682   QgsNewMeshLayerDialog dialog( this );
6683   dialog.setCrs( QgsProject::instance()->defaultCrsForNewLayers() );
6684   dialog.exec();
6685 }
6686 
newGpxLayer()6687 void QgisApp::newGpxLayer()
6688 {
6689   QgsSettings settings;
6690   const QString dir = settings.value( QStringLiteral( "gps/gpxdirectory" ), QDir::homePath(), QgsSettings::App ).toString();
6691   QString fileName =
6692     QFileDialog::getSaveFileName( this,
6693                                   tr( "New GPX File" ),
6694                                   dir,
6695                                   tr( "GPS eXchange file" ) + " (*.gpx)" );
6696   if ( !fileName.isEmpty() )
6697   {
6698     fileName = QgsFileUtils::ensureFileNameHasExtension( fileName, { QStringLiteral( "gpx" )} );
6699     const QFileInfo fileInfo( fileName );
6700     settings.setValue( QStringLiteral( "gps/gpxdirectory" ), fileInfo.absolutePath(), QgsSettings::App );
6701 
6702     QFile outputFile( fileName );
6703     if ( !outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
6704     {
6705       QMessageBox::warning( nullptr, tr( "New GPX File" ),
6706                             tr( "Unable to create a GPX file with the given name. "
6707                                 "Try again with another name or in another "
6708                                 "directory." ) );
6709       return;
6710     }
6711 
6712     QTextStream outStream( &outputFile );
6713 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
6714     outStream.setCodec( "UTF-8" );
6715 #endif
6716 
6717 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
6718     outStream << "<gpx></gpx>" << endl;
6719 #else
6720     outStream << "<gpx></gpx>" << Qt::endl;
6721 #endif
6722     outputFile.close();
6723 
6724     if ( QgsVectorLayer *trackLayer = addVectorLayer( fileName + "?type=track",
6725                                       fileInfo.baseName() + ", tracks", QStringLiteral( "gpx" ) ) )
6726       trackLayer->startEditing();
6727     if ( QgsVectorLayer *routeLayer = addVectorLayer( fileName + "?type=route",
6728                                       fileInfo.baseName() + ", routes", QStringLiteral( "gpx" ) ) )
6729       routeLayer->startEditing();
6730     if ( QgsVectorLayer *waypointLayer = addVectorLayer( fileName + "?type=waypoint",
6731                                          fileInfo.baseName() + ", waypoints", QStringLiteral( "gpx" ) ) )
6732       waypointLayer->startEditing();
6733   }
6734 }
6735 
showRasterCalculator()6736 void QgisApp::showRasterCalculator()
6737 {
6738   QgsRasterCalcDialog d( qobject_cast<QgsRasterLayer *>( activeLayer() ), this );
6739   if ( d.exec() != QDialog::Accepted )
6740   {
6741     return;
6742   }
6743   if ( d.useVirtualProvider() )
6744   {
6745     QgsRasterDataProvider::VirtualRasterParameters virtualCalcParams;
6746     virtualCalcParams.crs = d.outputCrs();
6747     virtualCalcParams.extent = d.outputRectangle();
6748     virtualCalcParams.width = d.numberOfColumns();
6749     virtualCalcParams.height = d.numberOfRows();
6750     virtualCalcParams.formula = d.formulaString();
6751 
6752     QString errorString;
6753     std::unique_ptr< QgsRasterCalcNode > calcNodeApp( QgsRasterCalcNode::parseRasterCalcString( d.formulaString(), errorString ) );
6754     if ( !calcNodeApp )
6755     {
6756       return;
6757     }
6758     QStringList rLayerDictionaryRef = calcNodeApp->cleanRasterReferences();
6759     QSet<QPair<QString, QString>> uniqueRasterUriTmp;
6760 
6761     for ( const auto &r : QgsRasterCalculatorEntry::rasterEntries() )
6762     {
6763       if ( ( ! rLayerDictionaryRef.contains( r.ref ) ) ||
6764            uniqueRasterUriTmp.contains( QPair( r.raster->source(), r.ref.mid( 0, r.ref.lastIndexOf( "@" ) ) ) ) ) continue;
6765       uniqueRasterUriTmp.insert( QPair( r.raster->source(), r.ref.mid( 0, r.ref.lastIndexOf( "@" ) ) ) );
6766 
6767       QgsRasterDataProvider::VirtualRasterInputLayers projectRLayer;
6768       projectRLayer.name = r.ref.mid( 0, r.ref.lastIndexOf( "@" ) );
6769       projectRLayer.provider = r.raster->dataProvider()->name();
6770       projectRLayer.uri = r.raster->source();
6771 
6772       virtualCalcParams.rInputLayers.append( projectRLayer );
6773     }
6774 
6775     addRasterLayer( QgsRasterDataProvider::encodeVirtualRasterProviderUri( virtualCalcParams ),
6776                     d.virtualLayerName().isEmpty() ? d.formulaString() : d.virtualLayerName(),
6777                     QStringLiteral( "virtualraster" ) );
6778   }
6779   else
6780   {
6781     //invoke analysis library
6782     QgsRasterCalculator rc( d.formulaString(),
6783                             d.outputFile(),
6784                             d.outputFormat(),
6785                             d.outputRectangle(),
6786                             d.outputCrs(),
6787                             d.numberOfColumns(),
6788                             d.numberOfRows(),
6789                             QgsRasterCalculatorEntry::rasterEntries(),
6790                             QgsProject::instance()->transformContext() );
6791 
6792     QProgressDialog p( tr( "Calculating raster expression…" ), tr( "Abort" ), 0, 0 );
6793     p.setWindowTitle( tr( "Raster calculator" ) );
6794     p.setWindowModality( Qt::WindowModal );
6795     p.setMaximum( 100.0 );
6796     QgsFeedback feedback;
6797     connect( &feedback, &QgsFeedback::progressChanged, &p, &QProgressDialog::setValue );
6798     connect( &p, &QProgressDialog::canceled, &feedback, &QgsFeedback::cancel );
6799     p.show();
6800     QgsRasterCalculator::Result res = rc.processCalculation( &feedback );
6801     switch ( res )
6802     {
6803       case QgsRasterCalculator::Success:
6804         if ( d.addLayerToProject() )
6805         {
6806           addRasterLayer( d.outputFile(), QFileInfo( d.outputFile() ).completeBaseName(), QStringLiteral( "gdal" ) );
6807         }
6808         visibleMessageBar()->pushMessage( tr( "Raster calculator" ),
6809                                           tr( "Calculation complete." ),
6810                                           Qgis::MessageLevel::Success );
6811         break;
6812 
6813       case QgsRasterCalculator::CreateOutputError:
6814         visibleMessageBar()->pushMessage( tr( "Raster calculator" ),
6815                                           tr( "Could not create destination file." ),
6816                                           Qgis::MessageLevel::Critical );
6817         break;
6818 
6819       case QgsRasterCalculator::InputLayerError:
6820         visibleMessageBar()->pushMessage( tr( "Raster calculator" ),
6821                                           tr( "Could not read input layer." ),
6822                                           Qgis::MessageLevel::Critical );
6823         break;
6824 
6825       case QgsRasterCalculator::Canceled:
6826         break;
6827 
6828       case QgsRasterCalculator::ParserError:
6829         visibleMessageBar()->pushMessage( tr( "Raster calculator" ),
6830                                           tr( "Could not parse raster formula." ),
6831                                           Qgis::MessageLevel::Critical );
6832         break;
6833 
6834       case QgsRasterCalculator::MemoryError:
6835         visibleMessageBar()->pushMessage( tr( "Raster calculator" ),
6836                                           tr( "Insufficient memory available for operation." ),
6837                                           Qgis::MessageLevel::Critical );
6838         break;
6839 
6840       case QgsRasterCalculator::BandError:
6841         visibleMessageBar()->pushMessage( tr( "Raster calculator" ),
6842                                           tr( "Invalid band number for input layer." ),
6843                                           Qgis::MessageLevel::Critical );
6844         break;
6845 
6846       case QgsRasterCalculator::CalculationError:
6847         visibleMessageBar()->pushMessage( tr( "Raster calculator" ),
6848                                           tr( "An error occurred while performing the calculation." ),
6849                                           Qgis::MessageLevel::Critical );
6850         break;
6851     }
6852     p.hide();
6853   }
6854 }
6855 
showMeshCalculator()6856 void QgisApp::showMeshCalculator()
6857 {
6858   QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( activeLayer() );
6859   if ( meshLayer && meshLayer->isEditable() )
6860   {
6861     QMessageBox::information( this, tr( "Mesh Calculator" ), tr( "Mesh calculator with mesh layer in edit mode is not supported." ) );
6862     return;
6863   }
6864   QgsMeshCalculatorDialog d( meshLayer, this );
6865   if ( d.exec() == QDialog::Accepted )
6866   {
6867     //invoke analysis library
6868     std::unique_ptr<QgsMeshCalculator> calculator = d.calculator();
6869 
6870     QProgressDialog p( tr( "Calculating mesh expression…" ), tr( "Abort" ), 0, 0 );
6871     p.setWindowModality( Qt::WindowModal );
6872     p.setMaximum( 100.0 );
6873     QgsFeedback feedback;
6874     connect( &feedback, &QgsFeedback::progressChanged, &p, &QProgressDialog::setValue );
6875     connect( &p, &QProgressDialog::canceled, &feedback, &QgsFeedback::cancel );
6876     p.show();
6877     QgsMeshCalculator::Result res = calculator->processCalculation( &feedback );
6878     switch ( res )
6879     {
6880       case QgsMeshCalculator::Success:
6881         visibleMessageBar()->pushMessage( tr( "Mesh calculator" ),
6882                                           tr( "Calculation complete." ),
6883                                           Qgis::MessageLevel::Success );
6884         break;
6885 
6886       case QgsMeshCalculator::EvaluateError:
6887         visibleMessageBar()->pushMessage( tr( "Mesh calculator" ),
6888                                           tr( "Could not evaluate the formula." ),
6889                                           Qgis::MessageLevel::Critical );
6890         break;
6891 
6892       case QgsMeshCalculator::InvalidDatasets:
6893         visibleMessageBar()->pushMessage( tr( "Mesh calculator" ),
6894                                           tr( "Invalid or incompatible datasets used." ),
6895                                           Qgis::MessageLevel::Critical );
6896         break;
6897 
6898       case QgsMeshCalculator::CreateOutputError:
6899         visibleMessageBar()->pushMessage( tr( "Mesh calculator" ),
6900                                           tr( "Could not create destination file." ),
6901                                           Qgis::MessageLevel::Critical );
6902         break;
6903 
6904       case QgsMeshCalculator::InputLayerError:
6905         visibleMessageBar()->pushMessage( tr( "Mesh calculator" ),
6906                                           tr( "Could not read input layer." ),
6907                                           Qgis::MessageLevel::Critical );
6908         break;
6909 
6910       case QgsMeshCalculator::Canceled:
6911         break;
6912 
6913       case QgsMeshCalculator::ParserError:
6914         visibleMessageBar()->pushMessage( tr( "Mesh calculator" ),
6915                                           tr( "Could not parse mesh formula." ),
6916                                           Qgis::MessageLevel::Critical );
6917         break;
6918 
6919       case QgsMeshCalculator::MemoryError:
6920         visibleMessageBar()->pushMessage( tr( "Mesh calculator" ),
6921                                           tr( "Insufficient memory available for operation." ),
6922                                           Qgis::MessageLevel::Critical );
6923         break;
6924     }
6925     p.hide();
6926   }
6927 }
6928 
6929 
showAlignRasterTool()6930 void QgisApp::showAlignRasterTool()
6931 {
6932   QgsAlignRasterDialog dlg( this );
6933   dlg.exec();
6934 }
6935 
6936 
fileOpen()6937 void QgisApp::fileOpen()
6938 {
6939   if ( checkTasksDependOnProject() )
6940     return;
6941 
6942   // possibly save any pending work before opening a new project
6943   if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() )
6944   {
6945     // Retrieve last used project dir from persistent settings
6946     QgsSettings settings;
6947     QString lastUsedDir = settings.value( QStringLiteral( "UI/lastProjectDir" ), QDir::homePath() ).toString();
6948 
6949 
6950     QStringList fileFilters;
6951     QStringList extensions;
6952     fileFilters << tr( "QGIS files" ) + QStringLiteral( " (*.qgs *.qgz *.QGS *.QGZ)" );
6953     extensions << QStringLiteral( "qgs" ) << QStringLiteral( "qgz" );
6954     for ( QgsCustomProjectOpenHandler *handler : std::as_const( mCustomProjectOpenHandlers ) )
6955     {
6956       if ( handler )
6957       {
6958         const QStringList filters = handler->filters();
6959         fileFilters.append( filters );
6960         for ( const QString &filter : filters )
6961           extensions.append( QgsFileUtils::extensionsFromFilter( filter ) );
6962       }
6963     }
6964 
6965     // generate master "all projects" extension list
6966     QString allEntry = tr( "All Project Files" ) + QStringLiteral( " (" );
6967     for ( const QString &extension : extensions )
6968       allEntry += QStringLiteral( "*.%1 *.%2 " ).arg( extension.toLower(), extension.toUpper() );
6969     allEntry.chop( 1 ); // remove trailing ' '
6970     allEntry += ')';
6971     fileFilters.insert( 0, allEntry );
6972 
6973     QString fullPath = QFileDialog::getOpenFileName( this,
6974                        tr( "Open Project" ),
6975                        lastUsedDir,
6976                        fileFilters.join( QLatin1String( ";;" ) ) );
6977     if ( fullPath.isNull() )
6978     {
6979       return;
6980     }
6981 
6982     QFileInfo myFI( fullPath );
6983     QString myPath = myFI.path();
6984     // Persist last used project dir
6985     settings.setValue( QStringLiteral( "UI/lastProjectDir" ), myPath );
6986 
6987     // open the selected project
6988     addProject( fullPath );
6989   }
6990 }
6991 
fileRevert()6992 void QgisApp::fileRevert()
6993 {
6994   if ( QMessageBox::question( this, tr( "Revert Project" ),
6995                               tr( "Are you sure you want to discard all unsaved changes the current project?" ),
6996                               QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No )
6997     return;
6998 
6999   if ( !checkUnsavedLayerEdits() || !checkMemoryLayers() )
7000     return;
7001 
7002   // re-open the current project
7003   addProject( QgsProject::instance()->fileName() );
7004 }
7005 
enableProjectMacros()7006 void QgisApp::enableProjectMacros()
7007 {
7008   mPythonMacrosEnabled = true;
7009 
7010   // load macros
7011   QgsPythonRunner::run( QStringLiteral( "qgis.utils.reloadProjectMacros()" ) );
7012 }
7013 
addProject(const QString & projectFile)7014 bool QgisApp::addProject( const QString &projectFile )
7015 {
7016   QgsCanvasRefreshBlocker refreshBlocker;
7017 
7018   bool returnCode = false;
7019   std::unique_ptr< QgsProjectDirtyBlocker > dirtyBlocker = std::make_unique< QgsProjectDirtyBlocker >( QgsProject::instance() );
7020   QObject connectionScope; // manually control scope of layersChanged lambda connection - we need the connection automatically destroyed when this function finishes
7021   bool badLayersHandled = false;
7022   connect( mAppBadLayersHandler, &QgsHandleBadLayersHandler::layersChanged, &connectionScope, [&badLayersHandled] { badLayersHandled = true; } );
7023 
7024   // close the previous opened project if any
7025   closeProject();
7026 
7027   QFileInfo pfi( projectFile );
7028   mStatusBar->showMessage( tr( "Loading project: %1" ).arg( pfi.fileName() ) );
7029   qApp->processEvents();
7030 
7031   QApplication::setOverrideCursor( Qt::WaitCursor );
7032 
7033   bool autoSetupOnFirstLayer = mLayerTreeCanvasBridge->autoSetupOnFirstLayer();
7034   mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( false );
7035 
7036   // give custom handlers a chance first
7037   bool usedCustomHandler = false;
7038   bool customHandlerWantsThumbnail = false;
7039   QIcon customHandlerIcon;
7040   for ( QgsCustomProjectOpenHandler *handler : std::as_const( mCustomProjectOpenHandlers ) )
7041   {
7042     if ( handler && handler->handleProjectOpen( projectFile ) )
7043     {
7044       usedCustomHandler = true;
7045       customHandlerWantsThumbnail = handler->createDocumentThumbnailAfterOpen();
7046       customHandlerIcon = handler->icon();
7047       break;
7048     }
7049   }
7050 
7051   if ( !usedCustomHandler && !QgsProject::instance()->read( projectFile ) && !QgsZipUtils::isZipFile( projectFile ) )
7052   {
7053     QString backupFile = projectFile + "~";
7054     QString loadBackupPrompt;
7055     QMessageBox::StandardButtons buttons;
7056     if ( QFile( backupFile ).exists() )
7057     {
7058       loadBackupPrompt = "\n\n" + tr( "Do you want to open the backup file\n%1\ninstead?" ).arg( backupFile );
7059       buttons |= QMessageBox::Yes;
7060       buttons |= QMessageBox::No;
7061     }
7062     else
7063     {
7064       buttons |= QMessageBox::Ok;
7065     }
7066     QApplication::restoreOverrideCursor();
7067     mStatusBar->clearMessage();
7068 
7069     int r = QMessageBox::critical( this,
7070                                    tr( "Unable to open project" ),
7071                                    QgsProject::instance()->error() + loadBackupPrompt,
7072                                    buttons );
7073 
7074     if ( QMessageBox::Yes == r && addProject( backupFile ) )
7075     {
7076       // We loaded data from the backup file, but we pretend to work on the original project file.
7077       QgsProject::instance()->setFileName( projectFile );
7078       QgsProject::instance()->setDirty( true );
7079       mProjectLastModified = QgsProject::instance()->lastModified();
7080       returnCode = true;
7081     }
7082     else
7083     {
7084       returnCode = false;
7085     }
7086   }
7087   else
7088   {
7089 
7090     mProjectLastModified = QgsProject::instance()->lastModified();
7091 
7092     setTitleBarText_( *this );
7093     mOverviewCanvas->setBackgroundColor( QgsProject::instance()->backgroundColor() );
7094 
7095     applyProjectSettingsToCanvas( mMapCanvas );
7096 
7097     //load project scales
7098     bool projectScales = QgsProject::instance()->viewSettings()->useProjectScales();
7099     if ( projectScales )
7100     {
7101       mScaleWidget->updateScales();
7102     }
7103 
7104     mMapCanvas->updateScale();
7105     QgsDebugMsgLevel( QStringLiteral( "Scale restored..." ), 3 );
7106 
7107     mFilterLegendByMapContentAction->setChecked( QgsProject::instance()->readBoolEntry( QStringLiteral( "Legend" ), QStringLiteral( "filterByMap" ) ) );
7108 
7109     // Select the first layer
7110     if ( mLayerTreeView->layerTreeModel()->rootGroup()->findLayers().count() > 0 )
7111     {
7112       mLayerTreeView->setCurrentLayer( mLayerTreeView->layerTreeModel()->rootGroup()->findLayers().at( 0 )->layer() );
7113     }
7114 
7115     QgsSettings settings;
7116 
7117 #ifdef WITH_BINDINGS
7118     // does the project have any macros?
7119     if ( mPythonUtils && mPythonUtils->isEnabled() )
7120     {
7121       if ( !QgsProject::instance()->readEntry( QStringLiteral( "Macros" ), QStringLiteral( "/pythonCode" ), QString() ).isEmpty() )
7122       {
7123         auto lambda = []() {QgisApp::instance()->enableProjectMacros();};
7124         QgsGui::pythonMacroAllowed( lambda, mInfoBar );
7125       }
7126     }
7127 #endif
7128 
7129     // Check for missing layer widget dependencies
7130     const auto constVLayers { QgsProject::instance()->layers<QgsVectorLayer *>( ) };
7131     for ( QgsVectorLayer *vl : constVLayers )
7132     {
7133       if ( vl->isValid() )
7134       {
7135         resolveVectorLayerDependencies( vl );
7136       }
7137     }
7138 
7139     emit projectRead(); // let plug-ins know that we've read in a new
7140     // project so that they can check any project
7141     // specific plug-in state
7142 
7143     // add this to the list of recently used project files
7144     // if a custom handler was used, then we generate a thumbnail
7145     if ( !usedCustomHandler || !customHandlerWantsThumbnail )
7146       saveRecentProjectPath( false );
7147     else if ( !QgsProject::instance()->originalPath().isEmpty() )
7148     {
7149       // we have to delay the thumbnail creation until after the canvas has refreshed for the first time
7150       QMetaObject::Connection *connection = new QMetaObject::Connection();
7151       *connection = connect( mMapCanvas, &QgsMapCanvas::mapCanvasRefreshed, [ = ]()
7152       {
7153         QObject::disconnect( *connection );
7154         delete connection;
7155         saveRecentProjectPath( true, customHandlerIcon );
7156       } );
7157     }
7158 
7159     QApplication::restoreOverrideCursor();
7160 
7161     if ( autoSetupOnFirstLayer )
7162       mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( true );
7163 
7164     mStatusBar->showMessage( tr( "Project loaded" ), 3000 );
7165     returnCode = true;
7166   }
7167 
7168   if ( badLayersHandled )
7169   {
7170     dirtyBlocker.reset(); // allow project dirtying again
7171     QgsProject::instance()->setDirty( true );
7172   }
7173 
7174   return returnCode;
7175 } // QgisApp::addProject(QString projectFile)
7176 
7177 
7178 
fileSave()7179 bool QgisApp::fileSave()
7180 {
7181   // if we don't have a file name, then obviously we need to get one; note
7182   // that the project file name is reset to null in fileNew()
7183 
7184   if ( QgsProject::instance()->fileName().isNull() )
7185   {
7186     // Retrieve last used project dir from persistent settings
7187     QgsSettings settings;
7188     QString lastUsedDir = settings.value( QStringLiteral( "UI/lastProjectDir" ), QDir::homePath() ).toString();
7189 
7190     const QString qgsExt = tr( "QGIS files" ) + " (*.qgs)";
7191     const QString zipExt = tr( "QGZ files" ) + " (*.qgz)";
7192 
7193     QString exts;
7194     QgsProject::FileFormat defaultProjectFileFormat = settings.enumValue( QStringLiteral( "/qgis/defaultProjectFileFormat" ), QgsProject::FileFormat::Qgz );
7195     if ( defaultProjectFileFormat == QgsProject::FileFormat::Qgs )
7196     {
7197       exts = qgsExt + QStringLiteral( ";;" ) + zipExt;
7198     }
7199     else
7200     {
7201       exts = zipExt + QStringLiteral( ";;" ) + qgsExt;
7202     }
7203     QString filter;
7204     QString path = QFileDialog::getSaveFileName(
7205                      this,
7206                      tr( "Choose a QGIS project file" ),
7207                      lastUsedDir + '/' + QgsProject::instance()->title(),
7208                      exts, &filter );
7209     if ( path.isEmpty() )
7210       return false;
7211 
7212     QFileInfo fullPath;
7213     fullPath.setFile( path );
7214 
7215     // make sure we have the .qgs extension in the file name
7216     if ( filter == zipExt )
7217     {
7218       if ( fullPath.suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) != 0 )
7219         fullPath.setFile( fullPath.filePath() + ".qgz" );
7220     }
7221     else
7222     {
7223       if ( fullPath.suffix().compare( QLatin1String( "qgs" ), Qt::CaseInsensitive ) != 0 )
7224         fullPath.setFile( fullPath.filePath() + ".qgs" );
7225     }
7226 
7227     QgsProject::instance()->setFileName( fullPath.filePath() );
7228   }
7229   else
7230   {
7231     bool usingProjectStorage = QgsProject::instance()->projectStorage();
7232     bool fileExists = usingProjectStorage ? true : QFileInfo::exists( QgsProject::instance()->fileName() );
7233 
7234     if ( fileExists && !mProjectLastModified.isNull() && mProjectLastModified != QgsProject::instance()->lastModified() )
7235     {
7236       if ( QMessageBox::warning( this,
7237                                  tr( "Open a Project" ),
7238                                  tr( "The loaded project file on disk was meanwhile changed. Do you want to overwrite the changes?\n"
7239                                      "\nLast modification date on load was: %1"
7240                                      "\nCurrent last modification date is: %2" )
7241                                  .arg( QLocale::system().toString( mProjectLastModified, QLocale::LongFormat ),
7242                                        QLocale::system().toString( QgsProject::instance()->lastModified(), QLocale::LongFormat ) ),
7243                                  QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel )
7244         return false;
7245     }
7246 
7247     if ( fileExists && !usingProjectStorage && ! QFileInfo( QgsProject::instance()->fileName() ).isWritable() )
7248     {
7249       visibleMessageBar()->pushMessage( tr( "Insufficient permissions" ),
7250                                         tr( "The project file is not writable." ),
7251                                         Qgis::MessageLevel::Warning );
7252       return false;
7253     }
7254   }
7255 
7256   // Store current map view settings into the project
7257   QgsProject::instance()->viewSettings()->setDefaultViewExtent( QgsReferencedRectangle( mapCanvas()->extent(), QgsProject::instance()->crs() ) );
7258 
7259   if ( QgsProject::instance()->write() )
7260   {
7261     setTitleBarText_( *this ); // update title bar
7262     mStatusBar->showMessage( tr( "Saved project to: %1" ).arg( QDir::toNativeSeparators( QgsProject::instance()->fileName() ) ), 5000 );
7263 
7264     saveRecentProjectPath();
7265 
7266     mProjectLastModified = QgsProject::instance()->lastModified();
7267   }
7268   else
7269   {
7270     QMessageBox::critical( this,
7271                            tr( "Unable to save project %1" ).arg( QDir::toNativeSeparators( QgsProject::instance()->fileName() ) ),
7272                            QgsProject::instance()->error() );
7273     mProjectLastModified = QgsProject::instance()->lastModified();
7274     return false;
7275   }
7276 
7277   // run the saved project macro
7278   if ( mPythonMacrosEnabled )
7279   {
7280     QgsPythonRunner::run( QStringLiteral( "qgis.utils.saveProjectMacro();" ) );
7281   }
7282 
7283   return true;
7284 } // QgisApp::fileSave
7285 
fileSaveAs()7286 void QgisApp::fileSaveAs()
7287 {
7288   QString defaultPath;
7289   QgsSettings settings;
7290   // First priority is to default to same path as existing file
7291   const QString currentPath = QgsProject::instance()->absoluteFilePath();
7292   if ( !currentPath.isEmpty() )
7293   {
7294     defaultPath = currentPath;
7295   }
7296   else
7297   {
7298     // Retrieve last used project dir from persistent settings
7299     defaultPath = settings.value( QStringLiteral( "UI/lastProjectDir" ), QDir::homePath() ).toString();
7300     defaultPath += QString( '/' + QgsProject::instance()->title() );
7301   }
7302 
7303   const QString qgsExt = tr( "QGIS files" ) + " (*.qgs *.QGS)";
7304   const QString zipExt = tr( "QGZ files" ) + " (*.qgz)";
7305 
7306   QString exts;
7307   QgsProject::FileFormat defaultProjectFileFormat = settings.enumValue( QStringLiteral( "/qgis/defaultProjectFileFormat" ), QgsProject::FileFormat::Qgz );
7308   if ( defaultProjectFileFormat == QgsProject::FileFormat::Qgs )
7309   {
7310     exts = qgsExt + QStringLiteral( ";;" ) + zipExt;
7311   }
7312   else
7313   {
7314     exts = zipExt + QStringLiteral( ";;" ) + qgsExt;
7315   }
7316   QString filter;
7317   QString path = QFileDialog::getSaveFileName( this,
7318                  tr( "Save Project As" ),
7319                  defaultPath,
7320                  exts, &filter );
7321   if ( path.isEmpty() )
7322     return;
7323 
7324   QFileInfo fullPath( path );
7325 
7326   QgsSettings().setValue( QStringLiteral( "UI/lastProjectDir" ), fullPath.path() );
7327 
7328   if ( filter == zipExt )
7329   {
7330     if ( fullPath.suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) != 0 )
7331       fullPath.setFile( fullPath.filePath() + ".qgz" );
7332   }
7333   else // .qgs
7334   {
7335     if ( fullPath.suffix().compare( QLatin1String( "qgs" ), Qt::CaseInsensitive ) != 0 )
7336       fullPath.setFile( fullPath.filePath() + ".qgs" );
7337   }
7338 
7339   QgsProject::instance()->setFileName( fullPath.filePath() );
7340 
7341   if ( QgsProject::instance()->write() )
7342   {
7343     setTitleBarText_( *this ); // update title bar
7344     mStatusBar->showMessage( tr( "Saved project to: %1" ).arg( QDir::toNativeSeparators( QgsProject::instance()->fileName() ) ), 5000 );
7345     // add this to the list of recently used project files
7346     saveRecentProjectPath();
7347   }
7348   else
7349   {
7350     QMessageBox::critical( this,
7351                            tr( "Unable to save project %1" ).arg( QDir::toNativeSeparators( QgsProject::instance()->fileName() ) ),
7352                            QgsProject::instance()->error(),
7353                            QMessageBox::Ok,
7354                            Qt::NoButton );
7355   }
7356   mProjectLastModified = fullPath.lastModified();
7357 } // QgisApp::fileSaveAs
7358 
dxfExport()7359 void QgisApp::dxfExport()
7360 {
7361   QgsDxfExportDialog d;
7362   if ( d.exec() == QDialog::Accepted )
7363   {
7364     QgsDxfExport dxfExport;
7365 
7366     QgsMapSettings settings( mapCanvas()->mapSettings() );
7367     settings.setLayerStyleOverrides( QgsProject::instance()->mapThemeCollection()->mapThemeStyleOverrides( d.mapTheme() ) );
7368     dxfExport.setMapSettings( settings );
7369     dxfExport.addLayers( d.layers() );
7370     dxfExport.setSymbologyScale( d.symbologyScale() );
7371     dxfExport.setSymbologyExport( d.symbologyMode() );
7372     dxfExport.setLayerTitleAsName( d.layerTitleAsName() );
7373     dxfExport.setDestinationCrs( d.crs() );
7374     dxfExport.setForce2d( d.force2d() );
7375 
7376     QgsDxfExport::Flags flags = QgsDxfExport::Flags();
7377     if ( !d.useMText() )
7378       flags = flags | QgsDxfExport::FlagNoMText;
7379     dxfExport.setFlags( flags );
7380 
7381     if ( auto *lMapCanvas = mapCanvas() )
7382     {
7383       //extent
7384       if ( d.exportMapExtent() )
7385       {
7386         QgsCoordinateTransform t( lMapCanvas->mapSettings().destinationCrs(), d.crs(), QgsProject::instance() );
7387         dxfExport.setExtent( t.transformBoundingBox( lMapCanvas->extent() ) );
7388       }
7389     }
7390 
7391     QString fileName( d.saveFile() );
7392     if ( !fileName.endsWith( QLatin1String( ".dxf" ), Qt::CaseInsensitive ) )
7393       fileName += QLatin1String( ".dxf" );
7394     QFile dxfFile( fileName );
7395     QApplication::setOverrideCursor( Qt::BusyCursor );
7396     switch ( dxfExport.writeToFile( &dxfFile, d.encoding() ) )
7397     {
7398       case QgsDxfExport::ExportResult::Success:
7399         visibleMessageBar()->pushMessage( tr( "DXF export completed" ), Qgis::MessageLevel::Success );
7400         break;
7401 
7402       case QgsDxfExport::ExportResult::DeviceNotWritableError:
7403         visibleMessageBar()->pushMessage( tr( "DXF export failed, device is not writable" ), Qgis::MessageLevel::Critical );
7404         break;
7405 
7406       case QgsDxfExport::ExportResult::InvalidDeviceError:
7407         visibleMessageBar()->pushMessage( tr( "DXF export failed, the device is invalid" ), Qgis::MessageLevel::Critical );
7408         break;
7409 
7410       case QgsDxfExport::ExportResult::EmptyExtentError:
7411         visibleMessageBar()->pushMessage( tr( "DXF export failed, the extent could not be determined" ), Qgis::MessageLevel::Critical );
7412         break;
7413     }
7414     QApplication::restoreOverrideCursor();
7415   }
7416 }
7417 
dwgImport()7418 void QgisApp::dwgImport()
7419 {
7420   QgsDwgImportDialog d;
7421   d.exec();
7422 }
7423 
openLayerDefinition(const QString & path)7424 void QgisApp::openLayerDefinition( const QString &path )
7425 {
7426   QString errorMessage;
7427   QgsReadWriteContext context;
7428   bool loaded = false;
7429 
7430   QFile file( path );
7431   if ( !file.open( QIODevice::ReadOnly ) )
7432   {
7433     errorMessage = QStringLiteral( "Can not open file" );
7434   }
7435   else
7436   {
7437     QDomDocument doc;
7438     QString message;
7439     if ( !doc.setContent( &file, &message ) )
7440     {
7441       errorMessage = message;
7442     }
7443     else
7444     {
7445       QFileInfo fileinfo( file );
7446       QDir::setCurrent( fileinfo.absoluteDir().path() );
7447 
7448       context.setPathResolver( QgsPathResolver( path ) );
7449       context.setProjectTranslator( QgsProject::instance() );
7450 
7451       loaded = QgsLayerDefinition::loadLayerDefinition( doc, QgsProject::instance(), QgsProject::instance()->layerTreeRoot(), errorMessage, context );
7452     }
7453   }
7454 
7455   if ( loaded )
7456   {
7457     const QList< QgsReadWriteContext::ReadWriteMessage > messages = context.takeMessages();
7458     QVector< QgsReadWriteContext::ReadWriteMessage > shownMessages;
7459     for ( const QgsReadWriteContext::ReadWriteMessage &message : messages )
7460     {
7461       if ( shownMessages.contains( message ) )
7462         continue;
7463 
7464       visibleMessageBar()->pushMessage( QString(), message.message(), message.categories().join( '\n' ), message.level() );
7465 
7466       shownMessages.append( message );
7467     }
7468   }
7469   else if ( !loaded || !errorMessage.isEmpty() )
7470   {
7471     visibleMessageBar()->pushMessage( tr( "Error loading layer definition" ), errorMessage, Qgis::MessageLevel::Warning );
7472   }
7473 }
7474 
openTemplate(const QString & fileName)7475 void QgisApp::openTemplate( const QString &fileName )
7476 {
7477   QFile templateFile;
7478   templateFile.setFileName( fileName );
7479 
7480   if ( !templateFile.open( QIODevice::ReadOnly ) )
7481   {
7482     visibleMessageBar()->pushMessage( tr( "Load template" ), tr( "Could not read template file" ), Qgis::MessageLevel::Warning );
7483     return;
7484   }
7485 
7486   QDomDocument templateDoc;
7487   if ( !templateDoc.setContent( &templateFile, false ) )
7488   {
7489     visibleMessageBar()->pushMessage( tr( "Load template" ), tr( "Could not load template file" ), Qgis::MessageLevel::Warning );
7490     return;
7491   }
7492 
7493   QString title;
7494   QDomElement layoutElem = templateDoc.documentElement();
7495   if ( !layoutElem.isNull() )
7496     title = layoutElem.attribute( QStringLiteral( "name" ) );
7497 
7498   if ( !uniqueLayoutTitle( this, title, true, QgsMasterLayoutInterface::PrintLayout, title ) )
7499   {
7500     return;
7501   }
7502 
7503   //create new layout object
7504   std::unique_ptr< QgsPrintLayout > layout = std::make_unique< QgsPrintLayout >( QgsProject::instance() );
7505   bool loadedOk = false;
7506   layout->loadFromTemplate( templateDoc, QgsReadWriteContext(), true, &loadedOk );
7507   if ( loadedOk )
7508   {
7509     layout->setName( title );
7510 
7511     openLayoutDesignerDialog( layout.get() );
7512     QgsProject::instance()->layoutManager()->addLayout( layout.release() );
7513   }
7514   else
7515   {
7516     visibleMessageBar()->pushMessage( tr( "Load template" ), tr( "Could not load template file" ), Qgis::MessageLevel::Warning );
7517   }
7518 }
7519 
7520 // Open the project file corresponding to the
7521 // path at the given index in mRecentProjectPaths
openProject(QAction * action)7522 void QgisApp::openProject( QAction *action )
7523 {
7524   // possibly save any pending work before opening a different project
7525   Q_ASSERT( action );
7526   const QString project = action->data().toString().replace( "&&", "&" );
7527 
7528   if ( checkTasksDependOnProject() )
7529     return;
7530 
7531   if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() )
7532     addProject( project );
7533 }
7534 
runScript(const QString & filePath)7535 void QgisApp::runScript( const QString &filePath )
7536 {
7537 #ifdef WITH_BINDINGS
7538   if ( !mPythonUtils || !mPythonUtils->isEnabled() )
7539     return;
7540 
7541   QgsSettings settings;
7542   bool showScriptWarning = settings.value( QStringLiteral( "UI/showScriptWarning" ), true ).toBool();
7543 
7544   QMessageBox msgbox;
7545   if ( showScriptWarning )
7546   {
7547     msgbox.setWindowTitle( tr( "Security warning" ) );
7548     msgbox.setText( tr( "Executing a script from an untrusted source can harm your computer. Only continue if you trust the source of the script. Continue?" ) );
7549     msgbox.setIcon( QMessageBox::Icon::Warning );
7550     msgbox.addButton( QMessageBox::Yes );
7551     msgbox.addButton( QMessageBox::No );
7552     msgbox.setDefaultButton( QMessageBox::No );
7553     QCheckBox *cb = new QCheckBox( tr( "Don't show this again." ) );
7554     msgbox.setCheckBox( cb );
7555     msgbox.exec();
7556     settings.setValue( QStringLiteral( "UI/showScriptWarning" ), !msgbox.checkBox()->isChecked() );
7557   }
7558 
7559   if ( !showScriptWarning || msgbox.result() == QMessageBox::Yes )
7560   {
7561     mPythonUtils->runString( QString( "qgis.utils.run_script_from_file(\"%1\")" ).arg( filePath ),
7562                              tr( "Failed to run Python script:" ), false );
7563   }
7564 #else
7565   Q_UNUSED( filePath )
7566 #endif
7567 }
7568 
openProject(const QString & fileName)7569 void QgisApp::openProject( const QString &fileName )
7570 {
7571   QgsCanvasRefreshBlocker refreshBlocker;
7572   if ( checkTasksDependOnProject() )
7573     return;
7574 
7575   // possibly save any pending work before opening a different project
7576   if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() )
7577   {
7578     // error handling and reporting is in addProject() function
7579     addProject( fileName );
7580   }
7581 }
7582 
openLayer(const QString & fileName,bool allowInteractive)7583 bool QgisApp::openLayer( const QString &fileName, bool allowInteractive )
7584 {
7585   bool ok = false;
7586   const QFileInfo fileInfo( fileName );
7587 
7588   // highest priority = delegate to provider registry to handle
7589   const QList< QgsProviderRegistry::ProviderCandidateDetails > candidateProviders = QgsProviderRegistry::instance()->preferredProvidersForUri( fileName );
7590   if ( candidateProviders.size() == 1 && candidateProviders.at( 0 ).layerTypes().size() == 1 )
7591   {
7592     // one good candidate provider and possible layer type -- that makes things nice and easy!
7593     switch ( candidateProviders.at( 0 ).layerTypes().at( 0 ) )
7594     {
7595       case QgsMapLayerType::VectorLayer:
7596       case QgsMapLayerType::RasterLayer:
7597       case QgsMapLayerType::MeshLayer:
7598       case QgsMapLayerType::AnnotationLayer:
7599       case QgsMapLayerType::PluginLayer:
7600       case QgsMapLayerType::VectorTileLayer:
7601         // not supported here yet!
7602         break;
7603 
7604       case QgsMapLayerType::PointCloudLayer:
7605         ok = static_cast< bool >( addPointCloudLayerPrivate( fileName, fileInfo.completeBaseName(), candidateProviders.at( 0 ).metadata()->key(), false ) );
7606         break;
7607     }
7608   }
7609 
7610   if ( ok )
7611     return true;
7612 
7613   CPLPushErrorHandler( CPLQuietErrorHandler );
7614 
7615   // if needed prompt for zipitem layers
7616   QString vsiPrefix = QgsZipItem::vsiPrefix( fileName );
7617   if ( vsiPrefix == QLatin1String( "/vsizip/" ) || vsiPrefix == QLatin1String( "/vsitar/" ) )
7618   {
7619     if ( askUserForZipItemLayers( fileName, {} ) )
7620     {
7621       CPLPopErrorHandler();
7622       return true;
7623     }
7624   }
7625 
7626   if ( fileName.endsWith( QStringLiteral( ".mbtiles" ), Qt::CaseInsensitive ) )
7627   {
7628     QgsMbTiles reader( fileName );
7629     if ( reader.open() )
7630     {
7631       if ( reader.metadataValue( "format" ) == QLatin1String( "pbf" ) )
7632       {
7633         // these are vector tiles
7634         QUrlQuery uq;
7635         uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
7636         uq.addQueryItem( QStringLiteral( "url" ), fileName );
7637         const QgsVectorTileLayer::LayerOptions options( QgsProject::instance()->transformContext() );
7638         std::unique_ptr<QgsVectorTileLayer> vtLayer( new QgsVectorTileLayer( uq.toString(), fileInfo.completeBaseName(), options ) );
7639         if ( vtLayer->isValid() )
7640         {
7641           QgsProject::instance()->addMapLayer( vtLayer.release() );
7642           return true;
7643         }
7644       }
7645       else // raster tiles
7646       {
7647         // prefer to use WMS provider's implementation to open MBTiles rasters
7648         QUrlQuery uq;
7649         uq.addQueryItem( QStringLiteral( "type" ), QStringLiteral( "mbtiles" ) );
7650         uq.addQueryItem( QStringLiteral( "url" ), QUrl::fromLocalFile( fileName ).toString() );
7651         if ( addRasterLayer( uq.toString(), fileInfo.completeBaseName(), QStringLiteral( "wms" ) ) )
7652           return true;
7653       }
7654     }
7655   }
7656 
7657   QList< QgsProviderSublayerModel::NonLayerItem > nonLayerItems;
7658   if ( QgsProjectStorage *ps = QgsApplication::projectStorageRegistry()->projectStorageFromUri( fileName ) )
7659   {
7660     const QStringList projects = ps->listProjects( fileName );
7661     for ( const QString &project : projects )
7662     {
7663       QgsProviderSublayerModel::NonLayerItem projectItem;
7664       projectItem.setType( QStringLiteral( "project" ) );
7665       projectItem.setName( project );
7666       projectItem.setUri( QStringLiteral( "%1://%2?projectName=%3" ).arg( ps->type(), fileName, project ) );
7667       projectItem.setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconQgsProjectFile.svg" ) ) );
7668       nonLayerItems << projectItem;
7669     }
7670   }
7671 
7672   // query sublayers
7673   QList< QgsProviderSublayerDetails > sublayers = QgsProviderRegistry::instance()->querySublayers( fileName, Qgis::SublayerQueryFlag::IncludeSystemTables );
7674 
7675   if ( !sublayers.empty() || !nonLayerItems.empty() )
7676   {
7677     const bool detailsAreIncomplete = QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers, QgsProviderUtils::SublayerCompletenessFlag::IgnoreUnknownFeatureCount );
7678     const bool singleSublayerOnly = sublayers.size() == 1;
7679     QString groupName;
7680 
7681     if ( allowInteractive && ( !singleSublayerOnly || detailsAreIncomplete || !nonLayerItems.empty() ) )
7682     {
7683       // ask user for sublayers (unless user settings dictate otherwise!)
7684       switch ( shouldAskUserForSublayers( sublayers, !nonLayerItems.empty() ) )
7685       {
7686         case SublayerHandling::AskUser:
7687         {
7688           // prompt user for sublayers
7689           QgsProviderSublayersDialog dlg( fileName, fileName, sublayers, {}, this );
7690           dlg.setNonLayerItems( nonLayerItems );
7691 
7692           if ( dlg.exec() )
7693           {
7694             sublayers = dlg.selectedLayers();
7695             nonLayerItems = dlg.selectedNonLayerItems();
7696           }
7697           else
7698           {
7699             sublayers.clear(); // dialog was canceled, so don't add any sublayers
7700             nonLayerItems.clear();
7701           }
7702           groupName = dlg.groupName();
7703           break;
7704         }
7705 
7706         case SublayerHandling::LoadAll:
7707         {
7708           if ( detailsAreIncomplete )
7709           {
7710             // requery sublayers, resolving geometry types
7711             sublayers = QgsProviderRegistry::instance()->querySublayers( fileName, Qgis::SublayerQueryFlag::ResolveGeometryType );
7712           }
7713           break;
7714         }
7715 
7716         case SublayerHandling::AbortLoading:
7717           sublayers.clear(); // don't add any sublayers
7718           break;
7719       };
7720     }
7721     else if ( detailsAreIncomplete )
7722     {
7723       // requery sublayers, resolving geometry types
7724       sublayers = QgsProviderRegistry::instance()->querySublayers( fileName, Qgis::SublayerQueryFlag::ResolveGeometryType );
7725     }
7726 
7727     ok = true;
7728 
7729     // now add sublayers
7730     if ( !sublayers.empty() )
7731     {
7732       QgsCanvasRefreshBlocker refreshBlocker;
7733       QgsSettings settings;
7734 
7735       QString base = QgsProviderUtils::suggestLayerNameFromFilePath( fileName );
7736       if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
7737       {
7738         base = QgsMapLayer::formatLayerName( base );
7739       }
7740 
7741       addSublayers( sublayers, base, groupName );
7742       activateDeactivateLayerRelatedActions( activeLayer() );
7743     }
7744     else if ( !nonLayerItems.empty() )
7745     {
7746       QgsCanvasRefreshBlocker refreshBlocker;
7747       if ( checkTasksDependOnProject() )
7748         return true;
7749 
7750       // possibly save any pending work before opening a different project
7751       if ( checkUnsavedLayerEdits() && checkMemoryLayers() && saveDirty() )
7752       {
7753         // error handling and reporting is in addProject() function
7754         addProject( nonLayerItems.at( 0 ).uri() );
7755       }
7756       return true;
7757     }
7758   }
7759 
7760   CPLPopErrorHandler();
7761 
7762   if ( !ok )
7763   {
7764     // maybe a known file type, which couldn't be opened due to a missing dependency... (eg. las for a non-pdal-enabled build)
7765     QgsProviderRegistry::UnusableUriDetails details;
7766     if ( QgsProviderRegistry::instance()->handleUnusableUri( fileName, details ) )
7767     {
7768       ok = true;
7769 
7770       if ( details.detailedWarning.isEmpty() )
7771         visibleMessageBar()->pushMessage( QString(), details.warning, Qgis::MessageLevel::Critical );
7772       else
7773         visibleMessageBar()->pushMessage( QString(), details.warning, details.detailedWarning, Qgis::MessageLevel::Critical );
7774     }
7775   }
7776 
7777   if ( !ok )
7778   {
7779     // we have no idea what this file is...
7780     QgsMessageLog::logMessage( tr( "Unable to load %1" ).arg( fileName ) );
7781 
7782     const QString msg = tr( "%1 is not a valid or recognized data source." ).arg( fileName );
7783     visibleMessageBar()->pushMessage( tr( "Invalid Data Source" ), msg, Qgis::MessageLevel::Critical );
7784   }
7785 
7786   return ok;
7787 }
7788 
7789 
7790 // Open a file specified by a commandline argument, Drop or FileOpen event.
openFile(const QString & fileName,const QString & fileTypeHint)7791 void QgisApp::openFile( const QString &fileName, const QString &fileTypeHint )
7792 {
7793   // check to see if we are opening a project file
7794   QFileInfo fi( fileName );
7795   if ( fileTypeHint == QLatin1String( "project" ) || fi.suffix().compare( QLatin1String( "qgs" ), Qt::CaseInsensitive ) == 0 || fi.suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) == 0 )
7796   {
7797     QgsDebugMsgLevel( "Opening project " + fileName, 2 );
7798     openProject( fileName );
7799   }
7800   else if ( fi.suffix().compare( QLatin1String( "qlr" ), Qt::CaseInsensitive ) == 0 )
7801   {
7802     openLayerDefinition( fileName );
7803   }
7804   else if ( fi.suffix().compare( QLatin1String( "qpt" ), Qt::CaseInsensitive ) == 0 )
7805   {
7806     openTemplate( fileName );
7807   }
7808   else if ( fi.suffix().compare( QLatin1String( "py" ), Qt::CaseInsensitive ) == 0 )
7809   {
7810     runScript( fileName );
7811   }
7812   else
7813   {
7814     QgsDebugMsgLevel( "Adding " + fileName + " to the map canvas", 2 );
7815     openLayer( fileName, true );
7816   }
7817 }
7818 
newPrintLayout()7819 void QgisApp::newPrintLayout()
7820 {
7821   QString title;
7822   if ( !uniqueLayoutTitle( this, title, true, QgsMasterLayoutInterface::PrintLayout ) )
7823   {
7824     return;
7825   }
7826   createNewPrintLayout( title );
7827 }
7828 
newReport()7829 void QgisApp::newReport()
7830 {
7831   QString title;
7832   if ( !uniqueLayoutTitle( this, title, true, QgsMasterLayoutInterface::Report ) )
7833   {
7834     return;
7835   }
7836   createNewReport( title );
7837 }
7838 
disablePreviewMode()7839 void QgisApp::disablePreviewMode()
7840 {
7841   mMapCanvas->setPreviewModeEnabled( false );
7842 }
7843 
activateMonoPreview()7844 void QgisApp::activateMonoPreview()
7845 {
7846   mMapCanvas->setPreviewModeEnabled( true );
7847   mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewMono );
7848 }
7849 
activateGrayscalePreview()7850 void QgisApp::activateGrayscalePreview()
7851 {
7852   mMapCanvas->setPreviewModeEnabled( true );
7853   mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewGrayscale );
7854 }
7855 
activateProtanopePreview()7856 void QgisApp::activateProtanopePreview()
7857 {
7858   mMapCanvas->setPreviewModeEnabled( true );
7859   mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewProtanope );
7860 }
7861 
activateDeuteranopePreview()7862 void QgisApp::activateDeuteranopePreview()
7863 {
7864   mMapCanvas->setPreviewModeEnabled( true );
7865   mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewDeuteranope );
7866 }
7867 
activateTritanopePreview()7868 void QgisApp::activateTritanopePreview()
7869 {
7870   mMapCanvas->setPreviewModeEnabled( true );
7871   mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewTritanope );
7872 }
7873 
toggleFilterLegendByExpression(bool checked)7874 void QgisApp::toggleFilterLegendByExpression( bool checked )
7875 {
7876   QgsLayerTreeNode *node = mLayerTreeView->currentNode();
7877   if ( ! node )
7878     return;
7879 
7880   if ( QgsLayerTree::isLayer( node ) )
7881   {
7882     QString e = mLegendExpressionFilterButton->expressionText();
7883     QgsLayerTreeUtils::setLegendFilterByExpression( *QgsLayerTree::toLayer( node ), e, checked );
7884   }
7885 
7886   updateFilterLegend();
7887 }
7888 
updateFilterLegend()7889 void QgisApp::updateFilterLegend()
7890 {
7891   bool hasExpressions = mLegendExpressionFilterButton->isChecked() && QgsLayerTreeUtils::hasLegendFilterExpression( *mLayerTreeView->layerTreeModel()->rootGroup() );
7892   if ( mFilterLegendByMapContentAction->isChecked() || hasExpressions )
7893   {
7894     layerTreeView()->layerTreeModel()->setLegendFilter( &mMapCanvas->mapSettings(),
7895         /* useExtent */ mFilterLegendByMapContentAction->isChecked(),
7896         /* polygon */ QgsGeometry(),
7897         hasExpressions );
7898   }
7899   else
7900   {
7901     layerTreeView()->layerTreeModel()->setLegendFilterByMap( nullptr );
7902   }
7903 }
7904 
activeDecorations()7905 QList< QgsMapDecoration * > QgisApp::activeDecorations()
7906 {
7907   QList< QgsMapDecoration * > decorations;
7908   const auto constMDecorationItems = mDecorationItems;
7909   for ( QgsDecorationItem *decoration : constMDecorationItems )
7910   {
7911     if ( decoration->enabled() )
7912     {
7913       decorations << decoration;
7914     }
7915   }
7916   return decorations;
7917 }
saveMapAsImage()7918 void QgisApp::saveMapAsImage()
7919 {
7920   QgsMapSaveDialog *dlg = new QgsMapSaveDialog( this, mMapCanvas, activeDecorations(), QgsProject::instance()->annotationManager()->annotations() );
7921   dlg->setAttribute( Qt::WA_DeleteOnClose );
7922   dlg->show();
7923 } // saveMapAsImage
7924 
saveMapAsPdf()7925 void QgisApp::saveMapAsPdf()
7926 {
7927   QgsMapSaveDialog *dlg = new QgsMapSaveDialog( this, mMapCanvas, activeDecorations(), QgsProject::instance()->annotationManager()->annotations(), QgsMapSaveDialog::Pdf );
7928   dlg->setAttribute( Qt::WA_DeleteOnClose );
7929   dlg->show();
7930 } // saveMapAsPdf
7931 
7932 //overloaded version of the above function
saveMapAsImage(const QString & imageFileNameQString,QPixmap * theQPixmap)7933 void QgisApp::saveMapAsImage( const QString &imageFileNameQString, QPixmap *theQPixmap )
7934 {
7935   if ( imageFileNameQString.isEmpty() )
7936   {
7937     //no fileName chosen
7938     return;
7939   }
7940   else
7941   {
7942     //force the size of the canvas
7943     mMapCanvas->resize( theQPixmap->width(), theQPixmap->height() );
7944     //save the mapview to the selected file
7945     mMapCanvas->saveAsImage( imageFileNameQString, theQPixmap );
7946   }
7947 } // saveMapAsImage
7948 
7949 //reimplements method from base (gui) class
addAllToOverview()7950 void QgisApp::addAllToOverview()
7951 {
7952   if ( mLayerTreeView )
7953   {
7954     const auto constFindLayers = mLayerTreeView->layerTreeModel()->rootGroup()->findLayers();
7955     for ( QgsLayerTreeLayer *nodeL : constFindLayers )
7956       nodeL->setCustomProperty( QStringLiteral( "overview" ), 1 );
7957   }
7958 
7959   markDirty();
7960 }
7961 
7962 //reimplements method from base (gui) class
removeAllFromOverview()7963 void QgisApp::removeAllFromOverview()
7964 {
7965   if ( mLayerTreeView )
7966   {
7967     const auto constFindLayers = mLayerTreeView->layerTreeModel()->rootGroup()->findLayers();
7968     for ( QgsLayerTreeLayer *nodeL : constFindLayers )
7969       nodeL->setCustomProperty( QStringLiteral( "overview" ), 0 );
7970   }
7971 
7972   markDirty();
7973 }
7974 
toggleFullScreen()7975 void QgisApp::toggleFullScreen()
7976 {
7977   QgsCanvasRefreshBlocker refreshBlocker;
7978   if ( mFullScreenMode )
7979   {
7980     if ( mPrevScreenModeMaximized )
7981     {
7982       // Change to maximized state. Just calling showMaximized() results in
7983       // the window going to the normal state. Calling showNormal() then
7984       // showMaxmized() is a work-around. Turn off rendering for this as it
7985       // would otherwise cause two re-renders of the map, which can take a
7986       // long time.
7987       showNormal();
7988       showMaximized();
7989       mPrevScreenModeMaximized = false;
7990     }
7991     else
7992     {
7993       showNormal();
7994     }
7995     mFullScreenMode = false;
7996   }
7997   else
7998   {
7999     if ( isMaximized() )
8000     {
8001       mPrevScreenModeMaximized = true;
8002     }
8003     showFullScreen();
8004     mFullScreenMode = true;
8005   }
8006 }
8007 
togglePanelsVisibility()8008 void QgisApp::togglePanelsVisibility()
8009 {
8010   toggleReducedView( false );
8011 }
8012 
toggleMapOnly()8013 void QgisApp::toggleMapOnly()
8014 {
8015   toggleReducedView( true );
8016 }
8017 
toggleReducedView(bool viewMapOnly)8018 void QgisApp::toggleReducedView( bool viewMapOnly )
8019 {
8020   QgsSettings settings;
8021 
8022   QStringList docksTitle = settings.value( QStringLiteral( "UI/hiddenDocksTitle" ), QStringList() ).toStringList();
8023   QStringList docksActive = settings.value( QStringLiteral( "UI/hiddenDocksActive" ), QStringList() ).toStringList();
8024   QStringList toolBarsActive = settings.value( QStringLiteral( "UI/hiddenToolBarsActive" ), QStringList() ).toStringList();
8025 
8026   const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
8027   const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
8028   const QList<QToolBar *> toolBars = findChildren<QToolBar *>();
8029 
8030   bool allWidgetsVisible = settings.value( QStringLiteral( "UI/allWidgetsVisible" ), true ).toBool();
8031 
8032   if ( allWidgetsVisible )  // that is: currently nothing is hidden
8033   {
8034 
8035     if ( viewMapOnly )  //
8036     {
8037       // hide also statusbar and menubar and all toolbars
8038       for ( QToolBar *toolBar : toolBars )
8039       {
8040         if ( toolBar->isVisible() && !toolBar->isFloating() && toolBar->parent()->inherits( "QMainWindow" ) )
8041         {
8042           // remember the active toolbars
8043           toolBarsActive << toolBar->windowTitle();
8044           toolBar->setVisible( false );
8045         }
8046       }
8047 
8048       this->menuBar()->setVisible( false );
8049       this->statusBar()->setVisible( false );
8050 
8051       settings.setValue( QStringLiteral( "UI/hiddenToolBarsActive" ), toolBarsActive );
8052     }
8053 
8054     for ( QDockWidget *dock : docks )
8055     {
8056       if ( dock->isVisible() && dockWidgetArea( dock ) != Qt::NoDockWidgetArea )
8057       {
8058         // remember the active docs
8059         docksTitle << dock->windowTitle();
8060         dock->setVisible( false );
8061       }
8062     }
8063 
8064     docksActive.reserve( tabBars.size() );
8065     for ( QTabBar *tabBar : tabBars )
8066     {
8067       // remember the active tab from the docks
8068       docksActive << tabBar->tabText( tabBar->currentIndex() );
8069     }
8070 
8071     settings.setValue( QStringLiteral( "UI/hiddenDocksTitle" ), docksTitle );
8072     settings.setValue( QStringLiteral( "UI/hiddenDocksActive" ), docksActive );
8073 
8074     settings.setValue( QStringLiteral( "UI/allWidgetsVisible" ), false );
8075   }
8076   else  // currently panels or other widgets are hidden: show ALL based on 'remembered UI settings'
8077   {
8078     for ( QDockWidget *dock : docks )
8079     {
8080       if ( docksTitle.contains( dock->windowTitle() ) )
8081       {
8082         dock->setVisible( true );
8083       }
8084     }
8085 
8086     for ( QTabBar *tabBar : tabBars )
8087     {
8088       for ( int i = 0; i < tabBar->count(); ++i )
8089       {
8090         if ( docksActive.contains( tabBar->tabText( i ) ) )
8091         {
8092           tabBar->setCurrentIndex( i );
8093         }
8094       }
8095     }
8096 
8097     for ( QToolBar *toolBar : toolBars )
8098     {
8099       if ( toolBarsActive.contains( toolBar->windowTitle() ) )
8100       {
8101         toolBar->setVisible( true );
8102       }
8103     }
8104     this->menuBar()->setVisible( true );
8105     this->statusBar()->setVisible( true );
8106 
8107     settings.remove( QStringLiteral( "UI/hiddenToolBarsActive" ) );
8108     settings.remove( QStringLiteral( "UI/hiddenDocksTitle" ) );
8109     settings.remove( QStringLiteral( "UI/hiddenDocksActive" ) );
8110 
8111     settings.setValue( QStringLiteral( "UI/allWidgetsVisible" ), true );
8112   }
8113 }
8114 
showActiveWindowMinimized()8115 void QgisApp::showActiveWindowMinimized()
8116 {
8117   QWidget *window = QApplication::activeWindow();
8118   if ( window )
8119   {
8120     window->showMinimized();
8121   }
8122 }
8123 
toggleActiveWindowMaximized()8124 void QgisApp::toggleActiveWindowMaximized()
8125 {
8126   QWidget *window = QApplication::activeWindow();
8127   if ( window )
8128   {
8129     if ( window->isMaximized() )
8130       window->showNormal();
8131     else
8132       window->showMaximized();
8133   }
8134 }
8135 
activate()8136 void QgisApp::activate()
8137 {
8138   raise();
8139   setWindowState( windowState() & ~Qt::WindowMinimized );
8140   activateWindow();
8141 }
8142 
bringAllToFront()8143 void QgisApp::bringAllToFront()
8144 {
8145   QgsGui::nativePlatformInterface()->currentAppActivateIgnoringOtherApps();
8146 }
8147 
addWindow(QAction * action)8148 void QgisApp::addWindow( QAction *action )
8149 {
8150 #ifdef Q_OS_MAC
8151   mWindowActions->addAction( action );
8152   mWindowMenu->addAction( action );
8153   action->setCheckable( true );
8154   action->setChecked( true );
8155 #else
8156   Q_UNUSED( action )
8157 #endif
8158 }
8159 
removeWindow(QAction * action)8160 void QgisApp::removeWindow( QAction *action )
8161 {
8162 #ifdef Q_OS_MAC
8163   mWindowActions->removeAction( action );
8164   mWindowMenu->removeAction( action );
8165 #else
8166   Q_UNUSED( action )
8167 #endif
8168 }
8169 
stopRendering()8170 void QgisApp::stopRendering()
8171 {
8172   const auto canvases = mapCanvases();
8173   for ( QgsMapCanvas *canvas : canvases )
8174     canvas->stopRendering();
8175 }
8176 
hideAllLayers()8177 void QgisApp::hideAllLayers()
8178 {
8179   QgsDebugMsgLevel( QStringLiteral( "hiding all layers!" ), 3 );
8180 
8181   const auto constChildren = mLayerTreeView->layerTreeModel()->rootGroup()->children();
8182   for ( QgsLayerTreeNode *node : constChildren )
8183   {
8184     node->setItemVisibilityCheckedRecursive( false );
8185   }
8186 }
8187 
showAllLayers()8188 void QgisApp::showAllLayers()
8189 {
8190   QgsDebugMsgLevel( QStringLiteral( "Showing all layers!" ), 3 );
8191   mLayerTreeView->layerTreeModel()->rootGroup()->setItemVisibilityCheckedRecursive( true );
8192 }
8193 
hideSelectedLayers()8194 void QgisApp::hideSelectedLayers()
8195 {
8196   QgsDebugMsgLevel( QStringLiteral( "hiding selected layers!" ), 3 );
8197 
8198   const auto constSelectedNodes = mLayerTreeView->selectedNodes();
8199   for ( QgsLayerTreeNode *node : constSelectedNodes )
8200   {
8201     node->setItemVisibilityChecked( false );
8202   }
8203 }
8204 
toggleSelectedLayers()8205 void QgisApp::toggleSelectedLayers()
8206 {
8207   QgsDebugMsgLevel( QStringLiteral( "toggling selected layers!" ), 3 );
8208 
8209   const auto constSelectedNodes = mLayerTreeView->selectedNodes();
8210   if ( ! constSelectedNodes.isEmpty() )
8211   {
8212     bool isFirstNodeChecked = constSelectedNodes[0]->itemVisibilityChecked();
8213     for ( QgsLayerTreeNode *node : constSelectedNodes )
8214     {
8215       node->setItemVisibilityChecked( ! isFirstNodeChecked );
8216     }
8217   }
8218 }
8219 
toggleSelectedLayersIndependently()8220 void QgisApp::toggleSelectedLayersIndependently()
8221 {
8222   QgsDebugMsgLevel( QStringLiteral( "toggling selected layers independently!" ), 3 );
8223 
8224   const auto constSelectedNodes = mLayerTreeView->selectedNodes();
8225   if ( ! constSelectedNodes.isEmpty() )
8226   {
8227     for ( QgsLayerTreeNode *node : constSelectedNodes )
8228     {
8229       node->setItemVisibilityChecked( ! node->itemVisibilityChecked() );
8230     }
8231   }
8232 }
8233 
hideDeselectedLayers()8234 void QgisApp::hideDeselectedLayers()
8235 {
8236   QList<QgsLayerTreeLayer *> selectedLayerNodes = mLayerTreeView->selectedLayerNodes();
8237 
8238   const auto constFindLayers = mLayerTreeView->layerTreeModel()->rootGroup()->findLayers();
8239   for ( QgsLayerTreeLayer *nodeLayer : constFindLayers )
8240   {
8241     if ( selectedLayerNodes.contains( nodeLayer ) )
8242       continue;
8243     nodeLayer->setItemVisibilityChecked( false );
8244   }
8245 }
8246 
showSelectedLayers()8247 void QgisApp::showSelectedLayers()
8248 {
8249   QgsDebugMsgLevel( QStringLiteral( "show selected layers!" ), 3 );
8250 
8251   const auto constSelectedNodes = mLayerTreeView->selectedNodes();
8252   for ( QgsLayerTreeNode *node : constSelectedNodes )
8253   {
8254     QgsLayerTreeNode *nodeIter = node;
8255     while ( nodeIter )
8256     {
8257       nodeIter->setItemVisibilityChecked( true );
8258       nodeIter = nodeIter->parent();
8259     }
8260   }
8261 }
8262 
8263 
zoomIn()8264 void QgisApp::zoomIn()
8265 {
8266   QgsDebugMsgLevel( QStringLiteral( "Setting map tool to zoomIn" ), 2 );
8267 
8268   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::ZoomIn ) );
8269 }
8270 
8271 
zoomOut()8272 void QgisApp::zoomOut()
8273 {
8274   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::ZoomOut ) );
8275 }
8276 
zoomToSelected()8277 void QgisApp::zoomToSelected()
8278 {
8279   const QList<QgsMapLayer *> layers = mLayerTreeView->selectedLayers();
8280 
8281   if ( layers.size() > 1 )
8282     mMapCanvas->zoomToSelected( layers );
8283 
8284   else
8285     mMapCanvas->zoomToSelected();
8286 
8287 }
8288 
panToSelected()8289 void QgisApp::panToSelected()
8290 {
8291   const QList<QgsMapLayer *> layers = mLayerTreeView->selectedLayers();
8292 
8293   if ( layers.size() > 1 )
8294     mMapCanvas->panToSelected( layers );
8295   else
8296     mMapCanvas->panToSelected();
8297 }
8298 
pan()8299 void QgisApp::pan()
8300 {
8301   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::Pan ) );
8302 }
8303 
zoomFull()8304 void QgisApp::zoomFull()
8305 {
8306   mMapCanvas->zoomToProjectExtent();
8307 }
8308 
zoomToPrevious()8309 void QgisApp::zoomToPrevious()
8310 {
8311   mMapCanvas->zoomToPreviousExtent();
8312 }
8313 
zoomToNext()8314 void QgisApp::zoomToNext()
8315 {
8316   mMapCanvas->zoomToNextExtent();
8317 }
8318 
zoomActualSize()8319 void QgisApp::zoomActualSize()
8320 {
8321   legendLayerZoomNative();
8322 }
8323 
identify()8324 void QgisApp::identify()
8325 {
8326   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::Identify ) );
8327 }
8328 
doFeatureAction()8329 void QgisApp::doFeatureAction()
8330 {
8331   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::FeatureAction ) );
8332 }
8333 
updateDefaultFeatureAction(QAction * action)8334 void QgisApp::updateDefaultFeatureAction( QAction *action )
8335 {
8336   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
8337   if ( !vlayer )
8338     return;
8339 
8340   mActionFeatureAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mAction.svg" ) ) );
8341   mActionFeatureAction->setToolTip( tr( "No action selected" ) );
8342 
8343   mFeatureActionMenu->setActiveAction( action );
8344 
8345   QgsAction qgsAction;
8346   if ( action )
8347   {
8348     qgsAction = action->data().value<QgsAction>();
8349   }
8350 
8351   if ( qgsAction.isValid() )
8352   {
8353     vlayer->actions()->setDefaultAction( QStringLiteral( "Canvas" ), qgsAction.id() );
8354     QgsGui::mapLayerActionRegistry()->setDefaultActionForLayer( vlayer, nullptr );
8355 
8356     mActionFeatureAction->setToolTip( tr( "Run feature action<br><b>%1</b>" ).arg( qgsAction.name() ) );
8357 
8358     if ( !qgsAction.icon().isNull() )
8359       mActionFeatureAction->setIcon( qgsAction.icon() );
8360     else
8361       mActionFeatureAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionActive.svg" ) ) );
8362   }
8363   else
8364   {
8365     //action is from QgsMapLayerActionRegistry
8366     vlayer->actions()->setDefaultAction( QStringLiteral( "Canvas" ), QString() );
8367 
8368     QgsMapLayerAction *mapLayerAction = qobject_cast<QgsMapLayerAction *>( action );
8369     if ( mapLayerAction )
8370     {
8371       QgsGui::mapLayerActionRegistry()->setDefaultActionForLayer( vlayer, mapLayerAction );
8372 
8373       if ( !mapLayerAction->text().isEmpty() )
8374         mActionFeatureAction->setToolTip( tr( "Run feature action<br><b>%1</b>" ).arg( mapLayerAction->text() ) );
8375 
8376       if ( !mapLayerAction->icon().isNull() )
8377         mActionFeatureAction->setIcon( mapLayerAction->icon() );
8378       else
8379         mActionFeatureAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionActive.svg" ) ) );
8380     }
8381     else
8382     {
8383       QgsGui::mapLayerActionRegistry()->setDefaultActionForLayer( vlayer, nullptr );
8384     }
8385   }
8386 }
8387 
refreshFeatureActions()8388 void QgisApp::refreshFeatureActions()
8389 {
8390   mFeatureActionMenu->clear();
8391 
8392   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
8393   if ( !vlayer )
8394     return;
8395 
8396   QList<QgsAction> actions = vlayer->actions()->actions( QStringLiteral( "Canvas" ) );
8397   const auto constActions = actions;
8398   for ( const QgsAction &action : constActions )
8399   {
8400     if ( !vlayer->isEditable() && action.isEnabledOnlyWhenEditable() )
8401       continue;
8402 
8403     QString actionTitle = !action.shortTitle().isEmpty() ? action.shortTitle() : action.icon().isNull() ? action.name() : QString();
8404     QAction *qAction = new QAction( action.icon(), actionTitle, mFeatureActionMenu );
8405     qAction->setData( QVariant::fromValue<QgsAction>( action ) );
8406     mFeatureActionMenu->addAction( qAction );
8407 
8408     if ( action.name() == vlayer->actions()->defaultAction( QStringLiteral( "Canvas" ) ).name() )
8409     {
8410       mFeatureActionMenu->setActiveAction( qAction );
8411     }
8412   }
8413 
8414   //add actions registered in QgsMapLayerActionRegistry
8415   QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( vlayer, QgsMapLayerAction::SingleFeature );
8416   if ( !actions.isEmpty() && !registeredActions.empty() )
8417   {
8418     //add a separator between user defined and standard actions
8419     mFeatureActionMenu->addSeparator();
8420   }
8421 
8422   for ( int i = 0; i < registeredActions.size(); i++ )
8423   {
8424     mFeatureActionMenu->addAction( registeredActions.at( i ) );
8425     if ( registeredActions.at( i ) == QgsGui::mapLayerActionRegistry()->defaultActionForLayer( vlayer ) )
8426     {
8427       mFeatureActionMenu->setActiveAction( registeredActions.at( i ) );
8428     }
8429   }
8430 
8431   updateDefaultFeatureAction( mFeatureActionMenu->activeAction() );
8432 }
8433 
changeDataSource(QgsMapLayer * layer)8434 void QgisApp::changeDataSource( QgsMapLayer *layer )
8435 {
8436   QgsMapLayerType layerType( layer->type() );
8437 
8438   QgsDataSourceSelectDialog dlg( mBrowserModel, true, layerType );
8439   if ( !layer->isValid() )
8440     dlg.setWindowTitle( tr( "Repair Data Source" ) );
8441 
8442   const QVariantMap sourceParts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->publicSource() );
8443   QString source = layer->publicSource();
8444   if ( sourceParts.contains( QStringLiteral( "path" ) ) )
8445   {
8446     const QString path = sourceParts.value( QStringLiteral( "path" ) ).toString();
8447     const QString closestPath = QFile::exists( path ) ? path : QgsFileUtils::findClosestExistingPath( path );
8448     source.replace( path, QStringLiteral( "<a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( closestPath ).toString(),
8449                     path ) );
8450   }
8451   dlg.setDescription( tr( "Original source URI: %1" ).arg( source ) );
8452 
8453   const QVariantMap originalSourceParts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
8454 
8455   if ( dlg.exec() == QDialog::Accepted )
8456   {
8457     QgsMimeDataUtils::Uri uri( dlg.uri() );
8458     if ( uri.isValid() )
8459     {
8460       auto fixLayer = [this]( QgsMapLayer * layer, const QgsMimeDataUtils::Uri & uri )
8461       {
8462         bool layerWasValid( layer->isValid() );
8463         // Store subset string from vlayer if we are fixing a bad layer
8464         QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
8465         QString subsetString;
8466         // Get the subset string directly from the data provider because
8467         // layer's method will return a null string from invalid layers
8468         if ( vlayer && vlayer->dataProvider() &&
8469              vlayer->dataProvider()->supportsSubsetString() &&
8470              !vlayer->dataProvider()->subsetString( ).isEmpty() )
8471         {
8472           subsetString = vlayer->dataProvider()->subsetString();
8473         }
8474         if ( vlayer && subsetString.isEmpty() )
8475         {
8476           // actually -- the above isn't true in all situations. If a layer was invalid at the time
8477           // that the subset string was set, then ONLY the layer has knowledge of this subset string!
8478           subsetString = vlayer->subsetString();
8479         }
8480 
8481         layer->setDataSource( uri.uri, layer->name(), uri.providerKey, QgsDataProvider::ProviderOptions() );
8482         // Re-apply original style and subset string  when fixing bad layers
8483         if ( !( layerWasValid || layer->originalXmlProperties().isEmpty() ) )
8484         {
8485           if ( ! subsetString.isEmpty() )
8486           {
8487             vlayer->setSubsetString( subsetString );
8488           }
8489           QgsReadWriteContext context;
8490           context.setPathResolver( QgsProject::instance()->pathResolver() );
8491           context.setProjectTranslator( QgsProject::instance() );
8492           QString errorMsg;
8493           QDomDocument doc;
8494           if ( doc.setContent( layer->originalXmlProperties() ) )
8495           {
8496             QDomNode layer_node( doc.firstChild( ) );
8497             if ( ! layer->readSymbology( layer_node, errorMsg, context ) )
8498             {
8499               QgsDebugMsg( QStringLiteral( "Failed to restore original layer style from stored XML for layer %1: %2" )
8500                            .arg( layer->name( ) )
8501                            .arg( errorMsg ) );
8502             }
8503           }
8504           else
8505           {
8506             QgsDebugMsg( QStringLiteral( "Failed to create XML QDomDocument for layer %1: %2" )
8507                          .arg( layer->name( ) )
8508                          .arg( errorMsg ) );
8509           }
8510         }
8511         else if ( !subsetString.isEmpty() )
8512         {
8513           vlayer->setSubsetString( subsetString );
8514         }
8515 
8516         if ( vlayer )
8517           vlayer->updateExtents();
8518 
8519         // All the following code is necessary to refresh the layer
8520         QgsLayerTreeModel *model = qobject_cast<QgsLayerTreeModel *>( mLayerTreeView->model() );
8521         if ( model )
8522         {
8523           QgsLayerTreeLayer *tl( model->rootGroup()->findLayer( layer->id() ) );
8524           if ( tl && tl->itemVisibilityChecked() )
8525           {
8526             tl->setItemVisibilityChecked( false );
8527             tl->setItemVisibilityChecked( true );
8528           }
8529         }
8530 
8531         // Tell the bridge that we have fixed a layer
8532         if ( ! layerWasValid && layer->isValid() )
8533         {
8534           QgsProject::instance()->layerTreeRoot()->customLayerOrderChanged( );
8535         }
8536       };
8537 
8538       fixLayer( layer, uri );
8539       const QVariantMap fixedUriParts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
8540 
8541       // next, we loop through to see if we can auto-fix any other layers with the same source
8542       if ( originalSourceParts.contains( QStringLiteral( "path" ) ) )
8543       {
8544         const QString originalPath = originalSourceParts.value( QStringLiteral( "path" ) ).toString();
8545         const QFileInfo originalPathFi( originalPath );
8546 
8547         const QMap< QString, QgsMapLayer * > layers = QgsProject::instance()->mapLayers( false );
8548         for ( auto it = layers.begin(); it != layers.end(); ++it )
8549         {
8550           if ( it.value()->isValid() )
8551             continue;
8552 
8553           QVariantMap thisParts = QgsProviderRegistry::instance()->decodeUri( it.value()->providerType(), it.value()->source() );
8554           if ( thisParts.contains( QStringLiteral( "path" ) ) )
8555           {
8556             const QString thisBrokenPath = thisParts.value( QStringLiteral( "path" ) ).toString();
8557             QString fixedPath;
8558 
8559             const QFileInfo thisBrokenPathFi( thisBrokenPath );
8560             if ( thisBrokenPath == originalPath )
8561             {
8562               // found a broken layer with the same original path, fix this one too
8563               fixedPath = fixedUriParts.value( QStringLiteral( "path" ) ).toString();
8564             }
8565             else if ( thisBrokenPathFi.path() == originalPathFi.path() )
8566             {
8567               // file from same original directory
8568               QDir fixedDir = QFileInfo( fixedUriParts.value( QStringLiteral( "path" ) ).toString() ).dir();
8569               const QString newCandidatePath = fixedDir.filePath( thisBrokenPathFi.fileName() );
8570               if ( QFileInfo::exists( newCandidatePath ) )
8571                 fixedPath = newCandidatePath;
8572             }
8573 
8574             if ( !fixedPath.isEmpty() )
8575             {
8576               uri.uri = it.value()->source().replace( thisBrokenPath, fixedPath );
8577               uri.providerKey = it.value()->providerType();
8578               fixLayer( it.value(), uri );
8579             }
8580           }
8581         }
8582       }
8583     }
8584   }
8585 }
8586 
measure()8587 void QgisApp::measure()
8588 {
8589   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::MeasureDistance ) );
8590 }
8591 
measureArea()8592 void QgisApp::measureArea()
8593 {
8594   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::MeasureArea ) );
8595 }
8596 
measureAngle()8597 void QgisApp::measureAngle()
8598 {
8599   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::MeasureAngle ) );
8600 }
8601 
addFormAnnotation()8602 void QgisApp::addFormAnnotation()
8603 {
8604   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::FormAnnotation ) );
8605 }
8606 
addHtmlAnnotation()8607 void QgisApp::addHtmlAnnotation()
8608 {
8609   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::HtmlAnnotation ) );
8610 }
8611 
addTextAnnotation()8612 void QgisApp::addTextAnnotation()
8613 {
8614   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::TextAnnotation ) );
8615 }
8616 
addSvgAnnotation()8617 void QgisApp::addSvgAnnotation()
8618 {
8619   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::SvgAnnotation ) );
8620 }
8621 
modifyAnnotation()8622 void QgisApp::modifyAnnotation()
8623 {
8624   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::Annotation ) );
8625 }
8626 
reprojectAnnotations()8627 void QgisApp::reprojectAnnotations()
8628 {
8629   const auto annotations = annotationItems();
8630   for ( QgsMapCanvasAnnotationItem *annotation : annotations )
8631   {
8632     annotation->updatePosition();
8633   }
8634 }
8635 
labelingFontNotFound(QgsVectorLayer * vlayer,const QString & fontfamily)8636 void QgisApp::labelingFontNotFound( QgsVectorLayer *vlayer, const QString &fontfamily )
8637 {
8638   // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
8639   QString substitute = tr( "Default system font substituted." );
8640 
8641   QToolButton *btnOpenPrefs = new QToolButton();
8642   btnOpenPrefs->setStyleSheet( QStringLiteral( "QToolButton{ background-color: rgba(255, 255, 255, 0); color: black; text-decoration: underline; }" ) );
8643   btnOpenPrefs->setCursor( Qt::PointingHandCursor );
8644   btnOpenPrefs->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
8645   btnOpenPrefs->setToolButtonStyle( Qt::ToolButtonTextOnly );
8646 
8647   // store pointer to vlayer in data of QAction
8648   QAction *act = new QAction( btnOpenPrefs );
8649   act->setData( QVariant( QMetaType::QObjectStar, &vlayer ) );
8650   act->setText( tr( "Open labeling dialog" ) );
8651   btnOpenPrefs->addAction( act );
8652   btnOpenPrefs->setDefaultAction( act );
8653   btnOpenPrefs->setToolTip( QString() );
8654   connect( btnOpenPrefs, &QToolButton::triggered, this, &QgisApp::labelingDialogFontNotFound );
8655 
8656   // no timeout set, since notice needs attention and is only shown first time layer is labeled
8657   QgsMessageBarItem *fontMsg = new QgsMessageBarItem(
8658     tr( "Labeling" ),
8659     tr( "Font for layer <b><u>%1</u></b> was not found (<i>%2</i>). %3" ).arg( vlayer->name(), fontfamily, substitute ),
8660     btnOpenPrefs,
8661     Qgis::MessageLevel::Warning,
8662     0,
8663     messageBar() );
8664   messageBar()->pushItem( fontMsg );
8665 }
8666 
commitError(QgsVectorLayer * vlayer)8667 void QgisApp::commitError( QgsVectorLayer *vlayer )
8668 {
8669   const QStringList commitErrors = vlayer->commitErrors();
8670   if ( !vlayer->allowCommit() && commitErrors.empty() )
8671   {
8672     return;
8673   }
8674 
8675   QgsMessageViewer *mv = new QgsMessageViewer();
8676   mv->setWindowTitle( tr( "Commit Errors" ) );
8677   mv->setMessageAsPlainText( tr( "Could not commit changes to layer %1" ).arg( vlayer->name() )
8678                              + "\n\n"
8679                              + tr( "Errors: %1\n" ).arg( commitErrors.join( QLatin1String( "\n  " ) ) )
8680                            );
8681 
8682   QToolButton *showMore = new QToolButton();
8683   // store pointer to vlayer in data of QAction
8684   QAction *act = new QAction( showMore );
8685   act->setData( QVariant( QMetaType::QObjectStar, &vlayer ) );
8686   act->setText( tr( "Show more" ) );
8687   showMore->setStyleSheet( QStringLiteral( "background-color: rgba(255, 255, 255, 0); color: black; text-decoration: underline;" ) );
8688   showMore->setCursor( Qt::PointingHandCursor );
8689   showMore->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
8690   showMore->addAction( act );
8691   showMore->setDefaultAction( act );
8692   connect( showMore, &QToolButton::triggered, mv, &QDialog::exec );
8693   connect( showMore, &QToolButton::triggered, showMore, &QObject::deleteLater );
8694 
8695   // no timeout set, since notice needs attention and is only shown first time layer is labeled
8696   QgsMessageBarItem *errorMsg = new QgsMessageBarItem(
8697     tr( "Commit errors" ),
8698     tr( "Could not commit changes to layer %1" ).arg( vlayer->name() ),
8699     showMore,
8700     Qgis::MessageLevel::Warning,
8701     0,
8702     messageBar() );
8703   messageBar()->pushItem( errorMsg );
8704 }
8705 
labelingDialogFontNotFound(QAction * act)8706 void QgisApp::labelingDialogFontNotFound( QAction *act )
8707 {
8708   if ( !act )
8709   {
8710     return;
8711   }
8712 
8713   // get base pointer to layer
8714   QObject *obj = qvariant_cast<QObject *>( act->data() );
8715 
8716   // remove calling messagebar widget
8717   messageBar()->popWidget();
8718 
8719   if ( !obj )
8720   {
8721     return;
8722   }
8723 
8724   QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( obj );
8725   if ( layer && setActiveLayer( layer ) )
8726   {
8727     labeling();
8728   }
8729 }
8730 
labeling()8731 void QgisApp::labeling()
8732 {
8733   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
8734   if ( !vlayer )
8735   {
8736     return;
8737   }
8738 
8739   mapStyleDock( true );
8740   mMapStyleWidget->setCurrentPage( QgsLayerStylingWidget::VectorLabeling );
8741 }
8742 
setMapStyleDockLayer(QgsMapLayer * layer)8743 void QgisApp::setMapStyleDockLayer( QgsMapLayer *layer )
8744 {
8745   if ( !layer )
8746   {
8747     return;
8748   }
8749 
8750   mMapStyleWidget->setEnabled( true );
8751   // We don't set the layer if the dock isn't open mainly to save
8752   // the extra work if it's not needed
8753   if ( mMapStylingDock->isVisible() )
8754   {
8755     mMapStyleWidget->setLayer( layer );
8756   }
8757 }
8758 
mapStyleDock(bool enabled)8759 void QgisApp::mapStyleDock( bool enabled )
8760 {
8761   mMapStylingDock->setUserVisible( enabled );
8762   setMapStyleDockLayer( activeLayer() );
8763 }
8764 
diagramProperties()8765 void QgisApp::diagramProperties()
8766 {
8767   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
8768   if ( !vlayer )
8769   {
8770     visibleMessageBar()->pushMessage( tr( "Diagram Properties" ),
8771                                       tr( "Please select a vector layer first" ),
8772                                       Qgis::MessageLevel::Info
8773                                     );
8774     return;
8775   }
8776 
8777   QDialog dlg;
8778   dlg.setWindowTitle( tr( "Layer Diagram Properties" ) );
8779   QgsDiagramProperties *gui = new QgsDiagramProperties( vlayer, &dlg, mMapCanvas );
8780   gui->layout()->setContentsMargins( 0, 0, 0, 0 );
8781   QVBoxLayout *layout = new QVBoxLayout( &dlg );
8782   layout->addWidget( gui );
8783 
8784   QDialogButtonBox *buttonBox = new QDialogButtonBox(
8785     QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply,
8786     Qt::Horizontal, &dlg );
8787   layout->addWidget( buttonBox );
8788 
8789   dlg.setLayout( layout );
8790 
8791   connect( buttonBox->button( QDialogButtonBox::Ok ), &QAbstractButton::clicked,
8792            &dlg, &QDialog::accept );
8793   connect( buttonBox->button( QDialogButtonBox::Cancel ), &QAbstractButton::clicked,
8794            &dlg, &QDialog::reject );
8795   connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked,
8796            gui, &QgsDiagramProperties::apply );
8797 
8798   if ( dlg.exec() )
8799     gui->apply();
8800 
8801   activateDeactivateLayerRelatedActions( vlayer );
8802 }
8803 
createAnnotationLayer()8804 void QgisApp::createAnnotationLayer()
8805 {
8806   // pick a unique name for the layer
8807   QString name = tr( "Annotations" );
8808   int id = 1;
8809   while ( !QgsProject::instance()->mapLayersByName( name ).isEmpty() )
8810   {
8811     name = tr( "Annotations (%1)" ).arg( id );
8812     id++;
8813   }
8814 
8815   QgsAnnotationLayer::LayerOptions options( QgsProject::instance()->transformContext() );
8816   QgsAnnotationLayer *layer = new QgsAnnotationLayer( name, options );
8817   layer->setCrs( QgsProject::instance()->crs() );
8818 
8819   // layer should be created at top of layer tree
8820   QgsProject::instance()->addMapLayer( layer, false );
8821   QgsProject::instance()->layerTreeRoot()->insertLayer( 0, layer );
8822 }
8823 
setCadDockVisible(bool visible)8824 void QgisApp::setCadDockVisible( bool visible )
8825 {
8826   mAdvancedDigitizingDockWidget->setVisible( visible );
8827 }
8828 
fieldCalculator()8829 void QgisApp::fieldCalculator()
8830 {
8831   QgsVectorLayer *myLayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
8832   if ( !myLayer )
8833   {
8834     return;
8835   }
8836 
8837   QgsFieldCalculator calc( myLayer, this );
8838   if ( calc.exec() )
8839   {
8840     myLayer->triggerRepaint();
8841   }
8842 }
8843 
attributeTable(QgsAttributeTableFilterModel::FilterMode filter)8844 void QgisApp::attributeTable( QgsAttributeTableFilterModel::FilterMode filter )
8845 {
8846   QgsVectorLayer *myLayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
8847   if ( !myLayer || !myLayer->dataProvider() )
8848   {
8849     return;
8850   }
8851 
8852   QgsAttributeTableDialog *mDialog = new QgsAttributeTableDialog( myLayer, filter );
8853   mDialog->show();
8854   // the dialog will be deleted by itself on close
8855 }
8856 
saveAsRasterFile(QgsRasterLayer * rasterLayer,const bool defaultAddToCanvas)8857 QString QgisApp::saveAsRasterFile( QgsRasterLayer *rasterLayer, const bool defaultAddToCanvas )
8858 {
8859   if ( !rasterLayer )
8860     rasterLayer = qobject_cast<QgsRasterLayer *>( activeLayer() );
8861 
8862   if ( !rasterLayer )
8863   {
8864     return QString();
8865   }
8866 
8867   QgsRasterLayerSaveAsDialog d( rasterLayer, rasterLayer->dataProvider(),
8868                                 mMapCanvas->extent(), rasterLayer->crs(),
8869                                 mMapCanvas->mapSettings().destinationCrs(),
8870                                 this );
8871   d.setAddToCanvas( defaultAddToCanvas );
8872   if ( d.exec() == QDialog::Rejected )
8873     return QString();
8874 
8875   QgsSettings settings;
8876   settings.setValue( QStringLiteral( "UI/lastRasterFileDir" ), QFileInfo( d.outputFileName() ).absolutePath() );
8877 
8878   QgsRasterFileWriter fileWriter( d.outputFileName() );
8879   if ( d.tileMode() )
8880   {
8881     fileWriter.setTiledMode( true );
8882     fileWriter.setMaxTileWidth( d.maximumTileSizeX() );
8883     fileWriter.setMaxTileHeight( d.maximumTileSizeY() );
8884   }
8885   else
8886   {
8887     fileWriter.setOutputFormat( d.outputFormat() );
8888   }
8889 
8890   // TODO: show error dialogs
8891   // TODO: this code should go somewhere else, but probably not into QgsRasterFileWriter
8892   // clone pipe/provider is not really necessary, ready for threads
8893   std::unique_ptr<QgsRasterPipe> pipe( nullptr );
8894 
8895   if ( d.mode() == QgsRasterLayerSaveAsDialog::RawDataMode )
8896   {
8897     QgsDebugMsgLevel( QStringLiteral( "Writing raw data" ), 2 );
8898     pipe.reset( new QgsRasterPipe() );
8899     if ( !pipe->set( rasterLayer->dataProvider()->clone() ) )
8900     {
8901       QgsDebugMsg( QStringLiteral( "Cannot set pipe provider" ) );
8902       return QString();
8903     }
8904 
8905     QgsRasterNuller *nuller = new QgsRasterNuller();
8906     for ( int band = 1; band <= rasterLayer->dataProvider()->bandCount(); band ++ )
8907     {
8908       nuller->setNoData( band, d.noData() );
8909     }
8910     if ( !pipe->insert( 1, nuller ) )
8911     {
8912       QgsDebugMsg( QStringLiteral( "Cannot set pipe nuller" ) );
8913       return QString();
8914     }
8915 
8916     // add projector if necessary
8917     if ( d.outputCrs() != rasterLayer->crs() )
8918     {
8919       QgsRasterProjector *projector = new QgsRasterProjector;
8920       projector->setCrs( rasterLayer->crs(), d.outputCrs(), QgsProject::instance()->transformContext() );
8921       if ( !pipe->insert( 2, projector ) )
8922       {
8923         QgsDebugMsg( QStringLiteral( "Cannot set pipe projector" ) );
8924         return QString();
8925       }
8926     }
8927   }
8928   else // RenderedImageMode
8929   {
8930     // clone the whole pipe
8931     QgsDebugMsgLevel( QStringLiteral( "Writing rendered image" ), 2 );
8932     pipe.reset( new QgsRasterPipe( *rasterLayer->pipe() ) );
8933     QgsRasterProjector *projector = pipe->projector();
8934     if ( !projector )
8935     {
8936       QgsDebugMsg( QStringLiteral( "Cannot get pipe projector" ) );
8937       return QString();
8938     }
8939     projector->setCrs( rasterLayer->crs(), d.outputCrs(), QgsProject::instance()->transformContext() );
8940   }
8941 
8942   if ( !pipe->last() )
8943   {
8944     return QString();
8945   }
8946   fileWriter.setCreateOptions( d.createOptions() );
8947 
8948   fileWriter.setBuildPyramidsFlag( d.buildPyramidsFlag() );
8949   fileWriter.setPyramidsList( d.pyramidsList() );
8950   fileWriter.setPyramidsResampling( d.pyramidsResamplingMethod() );
8951   fileWriter.setPyramidsFormat( d.pyramidsFormat() );
8952   fileWriter.setPyramidsConfigOptions( d.pyramidsConfigOptions() );
8953 
8954   bool tileMode = d.tileMode();
8955   bool addToCanvas = d.addToCanvas();
8956   QPointer< QgsRasterLayer > rlWeakPointer( rasterLayer );
8957   QString outputLayerName = d.outputLayerName();
8958   QString outputFormat = d.outputFormat();
8959 
8960   QgsRasterFileWriterTask *writerTask = new QgsRasterFileWriterTask( fileWriter, pipe.release(), d.nColumns(), d.nRows(),
8961       d.outputRectangle(), d.outputCrs(), QgsProject::instance()->transformContext() );
8962 
8963   // when writer is successful:
8964 
8965   connect( writerTask, &QgsRasterFileWriterTask::writeComplete, this,
8966            [this, tileMode, addToCanvas, rlWeakPointer, outputLayerName, outputFormat]( const QString & newFilename )
8967   {
8968     QString fileName = newFilename;
8969     if ( tileMode )
8970     {
8971       QFileInfo outputInfo( fileName );
8972       fileName = QStringLiteral( "%1/%2.vrt" ).arg( fileName, outputInfo.fileName() );
8973     }
8974 
8975     if ( addToCanvas )
8976     {
8977       if ( outputFormat == QLatin1String( "GPKG" ) && !outputLayerName.isEmpty() )
8978       {
8979         addRasterLayers( QStringList( QStringLiteral( "GPKG:%1:%2" ).arg( fileName, outputLayerName ) ) );
8980       }
8981       else
8982       {
8983         addRasterLayers( QStringList( fileName ) );
8984       }
8985     }
8986     if ( rlWeakPointer )
8987       emit layerSavedAs( rlWeakPointer, fileName );
8988 
8989     visibleMessageBar()->pushMessage( tr( "Layer Exported" ),
8990                                       tr( "Successfully saved raster layer to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( newFilename ).toString(), QDir::toNativeSeparators( newFilename ) ),
8991                                       Qgis::MessageLevel::Success, 0 );
8992   } );
8993 
8994   // when an error occurs:
8995   connect( writerTask, qOverload< int, const QString &>( &QgsRasterFileWriterTask::errorOccurred ), this, [ = ]( int error, const QString & errorMessage )
8996   {
8997     if ( error != QgsRasterFileWriter::WriteCanceled )
8998     {
8999       QString errorCodeStr;
9000       if ( error == QgsRasterFileWriter::SourceProviderError )
9001         errorCodeStr = tr( "source provider" );
9002       else if ( error == QgsRasterFileWriter::DestProviderError )
9003         errorCodeStr = tr( "destination provider" );
9004       else if ( error == QgsRasterFileWriter::CreateDatasourceError )
9005         errorCodeStr = tr( "data source creation" );
9006       else if ( error == QgsRasterFileWriter::WriteError )
9007         errorCodeStr = tr( "write error" );
9008       QString fullErrorMsg( tr( "Cannot write raster. Error code: %1" ).arg( errorCodeStr ) );
9009       if ( !errorMessage.isEmpty() )
9010         fullErrorMsg += "\n" + errorMessage;
9011       QMessageBox::warning( this, tr( "Save Raster" ),
9012                             fullErrorMsg,
9013                             QMessageBox::Ok );
9014     }
9015   } );
9016 
9017   QgsApplication::taskManager()->addTask( writerTask );
9018   return d.outputFileName();
9019 }
9020 
9021 
saveAsFile(QgsMapLayer * layer,const bool onlySelected,const bool defaultToAddToMap)9022 QString QgisApp::saveAsFile( QgsMapLayer *layer, const bool onlySelected, const bool defaultToAddToMap )
9023 {
9024   if ( !layer )
9025     layer = activeLayer();
9026 
9027   if ( !layer )
9028     return QString();
9029 
9030   QgsMapLayerType layerType = layer->type();
9031   switch ( layerType )
9032   {
9033     case QgsMapLayerType::RasterLayer:
9034       return saveAsRasterFile( qobject_cast<QgsRasterLayer *>( layer ), defaultToAddToMap );
9035 
9036     case QgsMapLayerType::VectorLayer:
9037       return saveAsVectorFileGeneral( qobject_cast<QgsVectorLayer *>( layer ), true, onlySelected, defaultToAddToMap );
9038 
9039     case QgsMapLayerType::MeshLayer:
9040     case QgsMapLayerType::VectorTileLayer:
9041     case QgsMapLayerType::PluginLayer:
9042     case QgsMapLayerType::AnnotationLayer:
9043     case QgsMapLayerType::PointCloudLayer:
9044       return QString();
9045   }
9046   return QString();
9047 }
9048 
makeMemoryLayerPermanent(QgsVectorLayer * layer)9049 void QgisApp::makeMemoryLayerPermanent( QgsVectorLayer *layer )
9050 {
9051   if ( !layer )
9052     return;
9053 
9054   const QString layerId = layer->id();
9055 
9056   auto onSuccess = [this, layerId]( const QString & newFilename,
9057                                     bool,
9058                                     const QString & newLayerName,
9059                                     const QString &,
9060                                     const QString & )
9061   {
9062     // we have to re-retrieve the layer, in case it's been removed during the lifetime of the writer task
9063     QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( QgsProject::instance()->mapLayer( layerId ) );
9064     if ( vl )
9065     {
9066       QgsDataProvider::ProviderOptions options;
9067       QString source = newFilename;
9068       if ( ! newLayerName.isEmpty() )
9069         source += QStringLiteral( "|layername=%1" ).arg( newLayerName );
9070       vl->setDataSource( source, vl->name(), QStringLiteral( "ogr" ), options );
9071       vl->triggerRepaint();
9072       mLayerTreeView->refreshLayerSymbology( vl->id() );
9073       this->visibleMessageBar()->pushMessage( tr( "Layer Saved" ),
9074                                               tr( "Successfully saved scratch layer to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( newFilename ).toString(), QDir::toNativeSeparators( newFilename ) ),
9075                                               Qgis::MessageLevel::Success, 0 );
9076     }
9077   };
9078 
9079   auto onFailure = []( int error, const QString & errorMessage )
9080   {
9081     if ( error != QgsVectorFileWriter::Canceled )
9082     {
9083       QgsMessageViewer *m = new QgsMessageViewer( nullptr );
9084       m->setWindowTitle( tr( "Save Error" ) );
9085       m->setMessageAsPlainText( tr( "Could not make temporary scratch layer permanent.\nError: %1" ).arg( errorMessage ) );
9086       m->exec();
9087     }
9088   };
9089 
9090   saveAsVectorFileGeneral( layer, true, false, true, onSuccess, onFailure, QgsVectorLayerSaveAsDialog::Options(), tr( "Save Scratch Layer" ) );
9091 }
9092 
saveAsLayerDefinition()9093 void QgisApp::saveAsLayerDefinition()
9094 {
9095   QgsSettings settings;
9096   QString lastUsedDir = settings.value( QStringLiteral( "UI/lastQLRDir" ), QDir::homePath() ).toString();
9097 
9098   QString path = QFileDialog::getSaveFileName( this, QStringLiteral( "Save as Layer Definition File" ), lastUsedDir, QStringLiteral( "*.qlr" ) );
9099   QgsDebugMsgLevel( path, 2 );
9100   if ( path.isEmpty() )
9101     return;
9102 
9103   QString errorMessage;
9104   bool saved = QgsLayerDefinition::exportLayerDefinition( path, mLayerTreeView->selectedNodes(), errorMessage );
9105   if ( !saved )
9106   {
9107     visibleMessageBar()->pushMessage( tr( "Error saving layer definition file" ), errorMessage, Qgis::MessageLevel::Warning );
9108   }
9109 
9110   QFileInfo fi( path );
9111   settings.setValue( QStringLiteral( "UI/lastQLRDir" ), fi.path() );
9112 }
9113 
saveStyleFile(QgsMapLayer * layer)9114 void QgisApp::saveStyleFile( QgsMapLayer *layer )
9115 {
9116   if ( !layer )
9117   {
9118     layer = activeLayer();
9119   }
9120 
9121   if ( !layer || !layer->dataProvider() )
9122     return;
9123 
9124   switch ( layer->type() )
9125   {
9126 
9127     case QgsMapLayerType::VectorLayer:
9128     {
9129       QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer );
9130       QgsVectorLayerSaveStyleDialog dlg( vlayer, this );
9131 
9132       if ( dlg.exec() )
9133       {
9134         bool resultFlag = false;
9135 
9136         QgsVectorLayerProperties::StyleType type = dlg.currentStyleType();
9137         switch ( type )
9138         {
9139           case QgsVectorLayerProperties::QML:
9140           case QgsVectorLayerProperties::SLD:
9141           {
9142             QString message;
9143             QString filePath = dlg.outputFilePath();
9144             if ( type == QgsVectorLayerProperties::QML )
9145               message = vlayer->saveNamedStyle( filePath, resultFlag, dlg.styleCategories() );
9146             else
9147               message = vlayer->saveSldStyle( filePath, resultFlag );
9148 
9149             if ( resultFlag )
9150             {
9151               mInfoBar->pushMessage( tr( "Style saved" ), tr( "Successfully exported style to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( filePath ).toString(), QDir::toNativeSeparators( filePath ) ), Qgis::MessageLevel::Success, 0 );
9152             }
9153             else
9154             {
9155               mInfoBar->pushMessage( tr( "Save Style" ), message, Qgis::MessageLevel::Warning );
9156             }
9157 
9158             break;
9159           }
9160           case QgsVectorLayerProperties::DB:
9161           {
9162             QString infoWindowTitle = QObject::tr( "Save style to DB (%1)" ).arg( vlayer->providerType() );
9163             QString msgError;
9164 
9165             QgsVectorLayerSaveStyleDialog::SaveToDbSettings dbSettings = dlg.saveToDbSettings();
9166 
9167             vlayer->saveStyleToDatabase( dbSettings.name, dbSettings.description, dbSettings.isDefault, dbSettings.uiFileContent, msgError );
9168 
9169             if ( !msgError.isNull() )
9170             {
9171               mInfoBar->pushMessage( infoWindowTitle, msgError, Qgis::MessageLevel::Warning );
9172             }
9173             else
9174             {
9175               mInfoBar->pushMessage( infoWindowTitle, tr( "Style saved" ), Qgis::MessageLevel::Success );
9176             }
9177             break;
9178           }
9179         }
9180       }
9181       break;
9182     }
9183 
9184     case QgsMapLayerType::RasterLayer:
9185     case QgsMapLayerType::MeshLayer:
9186     case QgsMapLayerType::PointCloudLayer:
9187     case QgsMapLayerType::VectorTileLayer:
9188     {
9189       QgsSettings settings;
9190       QString lastUsedDir = settings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString();
9191       QString filename = QFileDialog::getSaveFileName( this,
9192                          tr( "Save as QGIS Layer Style File" ),
9193                          lastUsedDir,
9194                          tr( "QGIS Layer Style File" ) + " (*.qml)" );
9195       if ( filename.isEmpty() )
9196         return;
9197 
9198       if ( ! filename.endsWith( QLatin1String( ".qml" ) ) )
9199       {
9200         filename += QLatin1String( ".qml" );
9201       }
9202 
9203       bool defaultLoadedFlag;
9204       layer->saveNamedStyle( filename, defaultLoadedFlag );
9205 
9206       settings.setValue( QStringLiteral( "style/lastStyleDir" ), filename );
9207       break;
9208     }
9209 
9210     case QgsMapLayerType::AnnotationLayer:
9211     case QgsMapLayerType::PluginLayer:
9212       break;
9213 
9214   }
9215 }
9216 
9217 ///@cond PRIVATE
9218 
9219 /**
9220  * Field value converter for export as vector layer
9221  * \note Not available in Python bindings
9222  */
9223 class QgisAppFieldValueConverter : public QgsVectorFileWriter::FieldValueConverter
9224 {
9225   public:
9226     QgisAppFieldValueConverter( QgsVectorLayer *vl, const QgsAttributeList &attributesAsDisplayedValues );
9227 
9228     QgsField fieldDefinition( const QgsField &field ) override;
9229 
9230     QVariant convert( int idx, const QVariant &value ) override;
9231 
9232     QgisAppFieldValueConverter *clone() const override;
9233 
9234   private:
9235     QPointer< QgsVectorLayer > mLayer;
9236     QgsAttributeList mAttributesAsDisplayedValues;
9237 };
9238 
QgisAppFieldValueConverter(QgsVectorLayer * vl,const QgsAttributeList & attributesAsDisplayedValues)9239 QgisAppFieldValueConverter::QgisAppFieldValueConverter( QgsVectorLayer *vl, const QgsAttributeList &attributesAsDisplayedValues )
9240   : mLayer( vl )
9241   , mAttributesAsDisplayedValues( attributesAsDisplayedValues )
9242 {
9243 }
9244 
fieldDefinition(const QgsField & field)9245 QgsField QgisAppFieldValueConverter::fieldDefinition( const QgsField &field )
9246 {
9247   if ( !mLayer )
9248     return field;
9249 
9250   int idx = mLayer->fields().indexFromName( field.name() );
9251   if ( mAttributesAsDisplayedValues.contains( idx ) )
9252   {
9253     return QgsField( field.name(), QVariant::String );
9254   }
9255   return field;
9256 }
9257 
convert(int idx,const QVariant & value)9258 QVariant QgisAppFieldValueConverter::convert( int idx, const QVariant &value )
9259 {
9260   if ( !mLayer || !mAttributesAsDisplayedValues.contains( idx ) )
9261   {
9262     return value;
9263   }
9264   const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, mLayer->fields().field( idx ).name() );
9265   QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
9266   return fieldFormatter->representValue( mLayer, idx, setup.config(), QVariant(), value );
9267 }
9268 
clone() const9269 QgisAppFieldValueConverter *QgisAppFieldValueConverter::clone() const
9270 {
9271   return new QgisAppFieldValueConverter( *this );
9272 }
9273 
9274 ///@endcond
9275 
saveAsVectorFileGeneral(QgsVectorLayer * vlayer,bool symbologyOption,bool onlySelected,bool defaultToAddToMap)9276 QString QgisApp::saveAsVectorFileGeneral( QgsVectorLayer *vlayer, bool symbologyOption, bool onlySelected, bool defaultToAddToMap )
9277 {
9278   if ( !vlayer )
9279   {
9280     vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() ); // FIXME: output of multiple layers at once?
9281   }
9282 
9283   if ( !vlayer )
9284     return QString();
9285 
9286   const QString layerId = vlayer->id();
9287 
9288   auto onSuccess = [this, layerId]( const QString & newFilename,
9289                                     bool addToCanvas,
9290                                     const QString & layerName,
9291                                     const QString & encoding,
9292                                     const QString & vectorFileName )
9293   {
9294     if ( addToCanvas )
9295     {
9296       QString uri( newFilename );
9297       if ( !layerName.isEmpty() )
9298         uri += "|layername=" + layerName;
9299       this->addVectorLayers( QStringList( uri ), encoding, QStringLiteral( "file" ) );
9300     }
9301 
9302     // We need to re-retrieve the map layer here, in case it's been deleted during the lifetime of the task
9303     if ( QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( QgsProject::instance()->mapLayer( layerId ) ) )
9304       this->emit layerSavedAs( vlayer, vectorFileName );
9305 
9306     this->visibleMessageBar()->pushMessage( tr( "Layer Exported" ),
9307                                             tr( "Successfully saved vector layer to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( newFilename ).toString(), QDir::toNativeSeparators( newFilename ) ),
9308                                             Qgis::MessageLevel::Success, 0 );
9309   };
9310 
9311   auto onFailure = []( int error, const QString & errorMessage )
9312   {
9313     if ( error != QgsVectorFileWriter::Canceled )
9314     {
9315       QgsMessageViewer *m = new QgsMessageViewer( nullptr );
9316       m->setWindowTitle( tr( "Save Error" ) );
9317       m->setMessageAsPlainText( tr( "Export to vector file failed.\nError: %1" ).arg( errorMessage ) );
9318       m->exec();
9319     }
9320   };
9321 
9322   return saveAsVectorFileGeneral( vlayer, symbologyOption, onlySelected, defaultToAddToMap, onSuccess, onFailure );
9323 }
9324 
saveAsVectorFileGeneral(QgsVectorLayer * vlayer,bool symbologyOption,bool onlySelected,bool defaultToAddToMap,const std::function<void (const QString &,bool,const QString &,const QString &,const QString &)> & onSuccess,const std::function<void (int,const QString &)> & onFailure,QgsVectorLayerSaveAsDialog::Options options,const QString & dialogTitle)9325 QString QgisApp::saveAsVectorFileGeneral( QgsVectorLayer *vlayer, bool symbologyOption, bool onlySelected, bool defaultToAddToMap, const std::function<void( const QString &, bool, const QString &, const QString &, const QString & )> &onSuccess, const std::function<void ( int, const QString & )> &onFailure, QgsVectorLayerSaveAsDialog::Options options, const QString &dialogTitle )
9326 {
9327   QgsCoordinateReferenceSystem destCRS;
9328 
9329   if ( !symbologyOption )
9330   {
9331     options &= ~QgsVectorLayerSaveAsDialog::Symbology;
9332   }
9333 
9334   QgsVectorLayerSaveAsDialog *dialog = new QgsVectorLayerSaveAsDialog( vlayer, options, this );
9335   if ( !dialogTitle.isEmpty() )
9336     dialog->setWindowTitle( dialogTitle );
9337 
9338   dialog->setMapCanvas( mMapCanvas );
9339   dialog->setIncludeZ( QgsWkbTypes::hasZ( vlayer->wkbType() ) );
9340   dialog->setOnlySelected( onlySelected );
9341   dialog->setAddToCanvas( defaultToAddToMap );
9342 
9343   QString vectorFilename;
9344   if ( dialog->exec() == QDialog::Accepted )
9345   {
9346     QString encoding = dialog->encoding();
9347     vectorFilename = dialog->filename();
9348     QString format = dialog->format();
9349     QStringList datasourceOptions = dialog->datasourceOptions();
9350     bool autoGeometryType = dialog->automaticGeometryType();
9351     QgsWkbTypes::Type forcedGeometryType = dialog->geometryType();
9352 
9353     QgsCoordinateTransform ct;
9354     destCRS = dialog->crsObject();
9355 
9356     if ( destCRS.isValid() )
9357     {
9358       QgsDatumTransformDialog::run( vlayer->crs(), destCRS, this, mMapCanvas );
9359       ct = QgsCoordinateTransform( vlayer->crs(), destCRS, QgsProject::instance() );
9360     }
9361 
9362     QgsRectangle filterExtent = dialog->filterExtent();
9363     QgisAppFieldValueConverter converter( vlayer, dialog->attributesAsDisplayedValues() );
9364     QgisAppFieldValueConverter *converterPtr = nullptr;
9365     // No need to use the converter if there is nothing to convert
9366     if ( !dialog->attributesAsDisplayedValues().isEmpty() )
9367       converterPtr = &converter;
9368 
9369     QgsVectorFileWriter::SaveVectorOptions options;
9370     options.driverName = format;
9371     options.layerName = dialog->layername();
9372     options.actionOnExistingFile = dialog->creationActionOnExistingFile();
9373     options.fileEncoding = encoding;
9374     options.ct = ct;
9375     options.onlySelectedFeatures = dialog->onlySelected();
9376     options.datasourceOptions = datasourceOptions;
9377     options.layerOptions = dialog->layerOptions();
9378     options.skipAttributeCreation = dialog->selectedAttributes().isEmpty();
9379     options.symbologyExport = static_cast< QgsVectorFileWriter::SymbologyExport >( dialog->symbologyExport() );
9380     options.symbologyScale = dialog->scale();
9381     if ( dialog->hasFilterExtent() )
9382       options.filterExtent = filterExtent;
9383     options.overrideGeometryType = autoGeometryType ? QgsWkbTypes::Unknown : forcedGeometryType;
9384     options.forceMulti = dialog->forceMulti();
9385     options.includeZ = dialog->includeZ();
9386     options.attributes = dialog->selectedAttributes();
9387     options.fieldValueConverter = converterPtr;
9388     options.saveMetadata = dialog->persistMetadata();
9389     options.layerMetadata = vlayer->metadata();
9390 
9391     bool addToCanvas = dialog->addToCanvas();
9392     QgsVectorFileWriterTask *writerTask = new QgsVectorFileWriterTask( vlayer, vectorFilename, options );
9393 
9394     // when writer is successful:
9395     connect( writerTask, &QgsVectorFileWriterTask::completed, this, [onSuccess, addToCanvas, encoding, vectorFilename]( const QString & newFilename, const QString & newLayer )
9396     {
9397       onSuccess( newFilename, addToCanvas, newLayer, encoding, vectorFilename );
9398     } );
9399 
9400     // when an error occurs:
9401     connect( writerTask, &QgsVectorFileWriterTask::errorOccurred, this, [onFailure]( int error, const QString & errorMessage )
9402     {
9403       onFailure( error, errorMessage );
9404     } );
9405 
9406     QgsApplication::taskManager()->addTask( writerTask );
9407   }
9408 
9409   delete dialog;
9410   return vectorFilename;
9411 }
9412 
layerProperties()9413 void QgisApp::layerProperties()
9414 {
9415   showLayerProperties( activeLayer() );
9416 }
9417 
deleteSelected(QgsMapLayer * layer,QWidget * parent,bool checkFeaturesVisible)9418 void QgisApp::deleteSelected( QgsMapLayer *layer, QWidget *parent, bool checkFeaturesVisible )
9419 {
9420   if ( !layer )
9421   {
9422     layer = mLayerTreeView->currentLayer();
9423   }
9424 
9425   if ( !parent )
9426   {
9427     parent = this;
9428   }
9429 
9430   if ( !layer )
9431   {
9432     visibleMessageBar()->pushMessage( tr( "No Layer Selected" ),
9433                                       tr( "To delete features, you must select a vector layer in the legend" ),
9434                                       Qgis::MessageLevel::Info );
9435     return;
9436   }
9437 
9438   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
9439   if ( !vlayer )
9440   {
9441     visibleMessageBar()->pushMessage( tr( "No Vector Layer Selected" ),
9442                                       tr( "Deleting features only works on vector layers" ),
9443                                       Qgis::MessageLevel::Info );
9444     return;
9445   }
9446 
9447   if ( !( vlayer->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteFeatures ) )
9448   {
9449     visibleMessageBar()->pushMessage( tr( "Provider does not support deletion" ),
9450                                       tr( "Data provider does not support deleting features" ),
9451                                       Qgis::MessageLevel::Info );
9452     return;
9453   }
9454 
9455   if ( !vlayer->isEditable() )
9456   {
9457     visibleMessageBar()->pushMessage( tr( "Layer not editable" ),
9458                                       tr( "The current layer is not editable. Choose 'Start editing' in the digitizing toolbar." ),
9459                                       Qgis::MessageLevel::Info );
9460     return;
9461   }
9462 
9463   //validate selection
9464   const int numberOfSelectedFeatures = vlayer->selectedFeatureCount();
9465   if ( numberOfSelectedFeatures == 0 )
9466   {
9467     visibleMessageBar()->pushMessage( tr( "No Features Selected" ),
9468                                       tr( "The current layer has no selected features" ),
9469                                       Qgis::MessageLevel::Info );
9470     return;
9471   }
9472   //display a warning
9473   if ( checkFeaturesVisible )
9474   {
9475     QgsFeature feat;
9476     QgsFeatureIterator it = vlayer->getSelectedFeatures( QgsFeatureRequest().setNoAttributes() );
9477     bool allFeaturesInView = true;
9478     QgsRectangle viewRect = mMapCanvas->mapSettings().mapToLayerCoordinates( vlayer, mMapCanvas->extent() );
9479 
9480     while ( it.nextFeature( feat ) )
9481     {
9482       if ( allFeaturesInView && !viewRect.intersects( feat.geometry().boundingBox() ) )
9483       {
9484         allFeaturesInView = false;
9485         break;
9486       }
9487     }
9488 
9489     if ( !allFeaturesInView )
9490     {
9491       // for extra safety to make sure we are not removing geometries by accident
9492       int res = QMessageBox::warning( mMapCanvas, tr( "Delete %n feature(s) from layer \"%1\"", nullptr, numberOfSelectedFeatures ).arg( vlayer->name() ),
9493                                       tr( "Some of the selected features are outside of the current map view. Would you still like to continue?" ),
9494                                       QMessageBox::Yes | QMessageBox::No );
9495       if ( res != QMessageBox::Yes )
9496         return;
9497     }
9498   }
9499 
9500   QgsVectorLayerUtils::QgsDuplicateFeatureContext infoContext;
9501   if ( QgsVectorLayerUtils::impactsCascadeFeatures( vlayer, vlayer->selectedFeatureIds(), QgsProject::instance(), infoContext, QgsVectorLayerUtils::IgnoreAuxiliaryLayers ) )
9502   {
9503     QString childrenInfo;
9504     int childrenCount = 0;
9505     const auto infoContextLayers = infoContext.layers();
9506     for ( QgsVectorLayer *chl : infoContextLayers )
9507     {
9508       childrenCount += infoContext.duplicatedFeatures( chl ).size();
9509       childrenInfo += ( tr( "%1 feature(s) on layer \"%2\", " ).arg( infoContext.duplicatedFeatures( chl ).size() ).arg( chl->name() ) );
9510     }
9511 
9512     // for extra safety to make sure we know that the delete can have impact on children and joins
9513     int res = QMessageBox::question( mMapCanvas, tr( "Delete at least %1 feature(s) on other layer(s)" ).arg( childrenCount ),
9514                                      tr( "Delete %1 feature(s) on layer \"%2\", %3 as well\nand all of its other descendants.\nDelete these features?" ).arg( numberOfSelectedFeatures ).arg( vlayer->name() ).arg( childrenInfo ),
9515                                      QMessageBox::Yes | QMessageBox::No );
9516     if ( res != QMessageBox::Yes )
9517       return;
9518   }
9519 
9520   vlayer->beginEditCommand( tr( "Features deleted" ) );
9521   int deletedCount = 0;
9522   QgsVectorLayer::DeleteContext context( true, QgsProject::instance() );
9523   if ( !vlayer->deleteSelectedFeatures( &deletedCount, &context ) )
9524   {
9525     visibleMessageBar()->pushMessage( tr( "Problem deleting features" ),
9526                                       tr( "A problem occurred during deletion from layer \"%1\". %n feature(s) not deleted.", nullptr, numberOfSelectedFeatures - deletedCount ).arg( vlayer->name() ),
9527                                       Qgis::MessageLevel::Warning );
9528   }
9529   else
9530   {
9531     const QList<QgsVectorLayer *> contextLayers = context.handledLayers( false );
9532     // if it affects more than one non-auxiliary layer, print feedback for all descendants
9533     if ( contextLayers.size() > 1 )
9534     {
9535       deletedCount = 0;
9536       QString feedbackMessage;
9537       for ( QgsVectorLayer *contextLayer : contextLayers )
9538       {
9539         feedbackMessage += tr( "%1 on layer %2. " ).arg( context.handledFeatures( contextLayer ).size() ).arg( contextLayer->name() );
9540         deletedCount += context.handledFeatures( contextLayer ).size();
9541       }
9542       visibleMessageBar()->pushMessage( tr( "%1 features deleted: %2" ).arg( deletedCount ).arg( feedbackMessage ), Qgis::MessageLevel::Success );
9543     }
9544 
9545     showStatusMessage( tr( "%n feature(s) deleted.", "number of features deleted", deletedCount ) );
9546   }
9547 
9548   vlayer->endEditCommand();
9549 }
9550 
moveFeature()9551 void QgisApp::moveFeature()
9552 {
9553   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::MoveFeature ) );
9554 }
9555 
moveFeatureCopy()9556 void QgisApp::moveFeatureCopy()
9557 {
9558   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::MoveFeatureCopy ) );
9559 }
9560 
offsetCurve()9561 void QgisApp::offsetCurve()
9562 {
9563   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::OffsetCurve ) );
9564 }
9565 
simplifyFeature()9566 void QgisApp::simplifyFeature()
9567 {
9568   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::SimplifyFeature ) );
9569 }
9570 
deleteRing()9571 void QgisApp::deleteRing()
9572 {
9573   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::DeleteRing ) );
9574 }
9575 
deletePart()9576 void QgisApp::deletePart()
9577 {
9578   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::DeletePart ) );
9579 }
9580 
reverseLine()9581 void QgisApp::reverseLine()
9582 {
9583   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::ReverseLine ) );
9584 }
9585 
unionGeometries(const QgsVectorLayer * vl,QgsFeatureList & featureList,bool & canceled)9586 QgsGeometry QgisApp::unionGeometries( const QgsVectorLayer *vl, QgsFeatureList &featureList, bool &canceled )
9587 {
9588   canceled = false;
9589   if ( !vl || featureList.size() < 2 )
9590   {
9591     return QgsGeometry();
9592   }
9593 
9594   if ( !featureList.at( 0 ).hasGeometry() )
9595     return QgsGeometry();
9596 
9597   QgsGeometry unionGeom = featureList.at( 0 ).geometry();
9598 
9599   QProgressDialog progress( tr( "Merging features…" ), tr( "Abort" ), 0, featureList.size(), this );
9600   progress.setWindowModality( Qt::WindowModal );
9601 
9602   QApplication::setOverrideCursor( Qt::WaitCursor );
9603 
9604   for ( int i = 1; i < featureList.size(); ++i )
9605   {
9606     if ( progress.wasCanceled() )
9607     {
9608       QApplication::restoreOverrideCursor();
9609       canceled = true;
9610       return QgsGeometry();
9611     }
9612     progress.setValue( i );
9613     QgsGeometry currentGeom = featureList.at( i ).geometry();
9614     if ( !currentGeom.isNull() )
9615     {
9616       unionGeom = unionGeom.combine( currentGeom );
9617       if ( unionGeom.isNull() )
9618       {
9619         QApplication::restoreOverrideCursor();
9620         return QgsGeometry();
9621       }
9622     }
9623   }
9624 
9625   //convert unionGeom to a multipart geometry in case it is necessary to match the layer type
9626   if ( QgsWkbTypes::isMultiType( vl->wkbType() ) && !unionGeom.isMultipart() )
9627   {
9628     unionGeom.convertToMultiType();
9629   }
9630 
9631   QApplication::restoreOverrideCursor();
9632   progress.setValue( featureList.size() );
9633   return unionGeom;
9634 }
9635 
uniqueLayoutTitle(QWidget * parent,QString & title,bool acceptEmpty,QgsMasterLayoutInterface::Type type,const QString & currentTitle)9636 bool QgisApp::uniqueLayoutTitle( QWidget *parent, QString &title, bool acceptEmpty, QgsMasterLayoutInterface::Type type, const QString &currentTitle )
9637 {
9638   if ( !parent )
9639   {
9640     parent = this;
9641   }
9642   bool titleValid = false;
9643   QString newTitle = QString( currentTitle );
9644 
9645   QString typeString;
9646   QString helpPage;
9647   switch ( type )
9648   {
9649     case QgsMasterLayoutInterface::PrintLayout:
9650       typeString = tr( "print layout" );
9651       helpPage = QStringLiteral( "print_composer/index.html" );
9652       break;
9653     case QgsMasterLayoutInterface::Report:
9654       typeString = tr( "report" );
9655       helpPage = QStringLiteral( "print_composer/create_reports.html" );
9656       break;
9657   }
9658 
9659   QString chooseMsg = tr( "Enter a unique %1 title" ).arg( typeString );
9660   if ( acceptEmpty )
9661   {
9662     chooseMsg += '\n' + tr( "(a title will be automatically generated if left empty)" );
9663   }
9664   QString titleMsg = chooseMsg;
9665 
9666   QStringList layoutNames;
9667   const QList< QgsMasterLayoutInterface * > layouts = QgsProject::instance()->layoutManager()->layouts();
9668   layoutNames.reserve( layouts.size() + 1 );
9669   for ( QgsMasterLayoutInterface *l : layouts )
9670   {
9671     layoutNames << l->name();
9672   }
9673 
9674   const QString windowTitle = tr( "Create %1" ).arg( QgsGui::higFlags() & QgsGui::HigDialogTitleIsTitleCase ? QgsStringUtils::capitalize( typeString, QgsStringUtils::TitleCase )
9675                               : typeString );
9676 
9677   while ( !titleValid )
9678   {
9679 
9680     QgsNewNameDialog dlg( typeString, newTitle, QStringList(), layoutNames, Qt::CaseSensitive, parent );
9681     dlg.setWindowTitle( windowTitle );
9682     dlg.setHintString( titleMsg );
9683     dlg.setOverwriteEnabled( false );
9684     dlg.setAllowEmptyName( true );
9685     dlg.setConflictingNameWarning( tr( "Title already exists!" ) );
9686 
9687     dlg.buttonBox()->addButton( QDialogButtonBox::Help );
9688     connect( dlg.buttonBox(), &QDialogButtonBox::helpRequested, this, [ = ]
9689     {
9690       QgsHelp::openHelp( helpPage );
9691     } );
9692 
9693     if ( dlg.exec() != QDialog::Accepted )
9694     {
9695       return false;
9696     }
9697 
9698     newTitle = dlg.name();
9699     if ( newTitle.isEmpty() )
9700     {
9701       if ( !acceptEmpty )
9702       {
9703         titleMsg = chooseMsg + "\n\n" + tr( "Title can not be empty!" );
9704       }
9705       else
9706       {
9707         titleValid = true;
9708         newTitle = QgsProject::instance()->layoutManager()->generateUniqueTitle( type );
9709       }
9710     }
9711     else if ( layoutNames.indexOf( newTitle, 1 ) >= 0 )
9712     {
9713       layoutNames[0] = QString(); // clear non-unique name
9714       titleMsg = chooseMsg + "\n\n" + tr( "Title already exists!" );
9715     }
9716     else
9717     {
9718       titleValid = true;
9719     }
9720   }
9721 
9722   title = newTitle;
9723 
9724   return true;
9725 }
9726 
createNewPrintLayout(const QString & t)9727 QgsLayoutDesignerDialog *QgisApp::createNewPrintLayout( const QString &t )
9728 {
9729   QString title = t;
9730   if ( title.isEmpty() )
9731   {
9732     title = QgsProject::instance()->layoutManager()->generateUniqueTitle( QgsMasterLayoutInterface::PrintLayout );
9733   }
9734   //create new layout object
9735   QgsPrintLayout *layout = new QgsPrintLayout( QgsProject::instance() );
9736   layout->setName( title );
9737   layout->initializeDefaults();
9738   if ( QgsProject::instance()->layoutManager()->addLayout( layout ) )
9739     return openLayoutDesignerDialog( layout );
9740   else
9741     return nullptr;
9742 }
9743 
createNewReport(QString title)9744 QgsLayoutDesignerDialog *QgisApp::createNewReport( QString title )
9745 {
9746   if ( title.isEmpty() )
9747   {
9748     title = QgsProject::instance()->layoutManager()->generateUniqueTitle( QgsMasterLayoutInterface::Report );
9749   }
9750   //create new report
9751   std::unique_ptr< QgsReport > report = std::make_unique< QgsReport >( QgsProject::instance() );
9752   report->setName( title );
9753   QgsMasterLayoutInterface *layout = report.get();
9754   QgsProject::instance()->layoutManager()->addLayout( report.release() );
9755   return openLayoutDesignerDialog( layout );
9756 }
9757 
openLayoutDesignerDialog(QgsMasterLayoutInterface * layout)9758 QgsLayoutDesignerDialog *QgisApp::openLayoutDesignerDialog( QgsMasterLayoutInterface *layout )
9759 {
9760   // maybe a designer already open for this layout
9761   const auto constMLayoutDesignerDialogs = mLayoutDesignerDialogs;
9762   for ( QgsLayoutDesignerDialog *designer : constMLayoutDesignerDialogs )
9763   {
9764     if ( designer->masterLayout() == layout )
9765     {
9766       designer->show();
9767       designer->activate();
9768       designer->raise();
9769       return designer;
9770     }
9771   }
9772 
9773   //nope, so make a new one
9774   //important - no parent set, otherwise Windows 10 sets the dialog as always on top of the QGIS window!!
9775   QgsLayoutDesignerDialog *newDesigner = new QgsLayoutDesignerDialog( nullptr );
9776   newDesigner->setMasterLayout( layout );
9777   connect( newDesigner, &QgsLayoutDesignerDialog::aboutToClose, this, [this, newDesigner]
9778   {
9779     emit layoutDesignerWillBeClosed( newDesigner->iface() );
9780     mLayoutDesignerDialogs.remove( newDesigner );
9781     emit layoutDesignerClosed();
9782   } );
9783 
9784   //add it to the map of existing print designers
9785   mLayoutDesignerDialogs.insert( newDesigner );
9786 
9787   newDesigner->open();
9788   emit layoutDesignerOpened( newDesigner->iface() );
9789 
9790   return newDesigner;
9791 }
9792 
duplicateLayout(QgsMasterLayoutInterface * layout,const QString & t)9793 QgsLayoutDesignerDialog *QgisApp::duplicateLayout( QgsMasterLayoutInterface *layout, const QString &t )
9794 {
9795   QString title = t;
9796   if ( title.isEmpty() )
9797   {
9798     // TODO: inject a bit of randomness in auto-titles?
9799     title = tr( "%1 copy" ).arg( layout->name() );
9800   }
9801 
9802   QgsMasterLayoutInterface *newLayout = QgsProject::instance()->layoutManager()->duplicateLayout( layout, title );
9803   QgsLayoutDesignerDialog *dlg = openLayoutDesignerDialog( newLayout );
9804   dlg->activate();
9805   return dlg;
9806 }
9807 
deleteLayoutDesigners()9808 void QgisApp::deleteLayoutDesigners()
9809 {
9810   // need a copy, since mLayoutDesignerDialogs will be modified as we iterate
9811   const QSet<QgsLayoutDesignerDialog *> dialogs = mLayoutDesignerDialogs;
9812   for ( QgsLayoutDesignerDialog *dlg : dialogs )
9813   {
9814     dlg->close(); // will trigger delete
9815   }
9816 }
9817 
setupLayoutManagerConnections()9818 void QgisApp::setupLayoutManagerConnections()
9819 {
9820   QgsLayoutManager *manager = QgsProject::instance()->layoutManager();
9821   connect( manager, &QgsLayoutManager::layoutAdded, this, [ = ]( const QString & name )
9822   {
9823     QgsMasterLayoutInterface *l = QgsProject::instance()->layoutManager()->layoutByName( name );
9824     if ( !l )
9825       return;
9826     QgsPrintLayout *pl = dynamic_cast< QgsPrintLayout *>( l );
9827     if ( !pl )
9828       return;
9829 
9830     mAtlasFeatureActions.insert( pl, nullptr );
9831     connect( pl, &QgsPrintLayout::nameChanged, this, [this, pl]( const QString & name )
9832     {
9833       QgsMapLayerAction *action = mAtlasFeatureActions.value( pl );
9834       if ( action )
9835       {
9836         action->setText( tr( "Set as atlas feature for %1" ).arg( name ) );
9837       }
9838     } );
9839 
9840     connect( pl->atlas(), &QgsLayoutAtlas::coverageLayerChanged, this, [this, pl]( QgsVectorLayer * coverageLayer )
9841     {
9842       setupAtlasMapLayerAction( pl, static_cast< bool >( coverageLayer ) );
9843     } );
9844 
9845     connect( pl->atlas(), &QgsLayoutAtlas::toggled, this, [this, pl]( bool enabled )
9846     {
9847       setupAtlasMapLayerAction( pl, enabled );
9848     } );
9849 
9850     setupAtlasMapLayerAction( pl, pl->atlas()->enabled() && pl->atlas()->coverageLayer() );
9851   } );
9852 
9853   connect( manager, &QgsLayoutManager::layoutAboutToBeRemoved, this, [ = ]( const QString & name )
9854   {
9855     QgsMasterLayoutInterface *l = QgsProject::instance()->layoutManager()->layoutByName( name );
9856     if ( l )
9857     {
9858       QgsPrintLayout *pl = dynamic_cast< QgsPrintLayout * >( l );
9859       if ( pl )
9860       {
9861         QgsMapLayerAction *action = mAtlasFeatureActions.value( pl );
9862         if ( action )
9863         {
9864           QgsGui::mapLayerActionRegistry()->removeMapLayerAction( action );
9865           delete action;
9866           mAtlasFeatureActions.remove( pl );
9867         }
9868       }
9869     }
9870   } );
9871 }
9872 
setupDuplicateFeaturesAction()9873 void QgisApp::setupDuplicateFeaturesAction()
9874 {
9875   mDuplicateFeatureAction.reset( new QgsMapLayerAction( tr( "Duplicate Feature" ),
9876                                  nullptr, QgsMapLayerAction::SingleFeature,
9877                                  QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeature.svg" ) ), QgsMapLayerAction::EnabledOnlyWhenEditable ) );
9878 
9879   QgsGui::mapLayerActionRegistry()->addMapLayerAction( mDuplicateFeatureAction.get() );
9880   connect( mDuplicateFeatureAction.get(), &QgsMapLayerAction::triggeredForFeature, this, [this]( QgsMapLayer * layer, const QgsFeature & feat )
9881   {
9882     duplicateFeatures( layer, feat );
9883   }
9884          );
9885 
9886   mDuplicateFeatureDigitizeAction.reset( new QgsMapLayerAction( tr( "Duplicate Feature and Digitize" ),
9887                                          nullptr, QgsMapLayerAction::SingleFeature,
9888                                          QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateFeatureDigitized.svg" ) ), QgsMapLayerAction::EnabledOnlyWhenEditable ) );
9889 
9890   QgsGui::mapLayerActionRegistry()->addMapLayerAction( mDuplicateFeatureDigitizeAction.get() );
9891   connect( mDuplicateFeatureDigitizeAction.get(), &QgsMapLayerAction::triggeredForFeature, this, [this]( QgsMapLayer * layer, const QgsFeature & feat )
9892   {
9893     duplicateFeatureDigitized( layer, feat );
9894   }
9895          );
9896 }
9897 
setupAtlasMapLayerAction(QgsPrintLayout * layout,bool enableAction)9898 void QgisApp::setupAtlasMapLayerAction( QgsPrintLayout *layout, bool enableAction )
9899 {
9900   QgsMapLayerAction *action = mAtlasFeatureActions.value( layout );
9901   if ( action )
9902   {
9903     QgsGui::mapLayerActionRegistry()->removeMapLayerAction( action );
9904     delete action;
9905     action = nullptr;
9906     mAtlasFeatureActions.remove( layout );
9907   }
9908 
9909   if ( enableAction )
9910   {
9911     action = new QgsMapLayerAction( tr( "Set as Atlas Feature for %1" ).arg( layout->name() ),
9912                                     this, layout->atlas()->coverageLayer(), QgsMapLayerAction::SingleFeature,
9913                                     QgsApplication::getThemeIcon( QStringLiteral( "/mIconAtlas.svg" ) ) );
9914     mAtlasFeatureActions.insert( layout, action );
9915     QgsGui::mapLayerActionRegistry()->addMapLayerAction( action );
9916     connect( action, &QgsMapLayerAction::triggeredForFeature, this, [this, layout]( QgsMapLayer * layer, const QgsFeature & feat )
9917     {
9918       Q_UNUSED( layer )
9919       setLayoutAtlasFeature( layout, feat );
9920     }
9921            );
9922   }
9923 }
9924 
setLayoutAtlasFeature(QgsPrintLayout * layout,const QgsFeature & feat)9925 void QgisApp::setLayoutAtlasFeature( QgsPrintLayout *layout, const QgsFeature &feat )
9926 {
9927   QgsLayoutDesignerDialog *designer = openLayoutDesignerDialog( layout );
9928   designer->setAtlasFeature( feat );
9929 }
9930 
layoutsMenuAboutToShow()9931 void QgisApp::layoutsMenuAboutToShow()
9932 {
9933   populateLayoutsMenu( mLayoutsMenu );
9934 }
9935 
populateLayoutsMenu(QMenu * menu)9936 void QgisApp::populateLayoutsMenu( QMenu *menu )
9937 {
9938   menu->clear();
9939   QList<QAction *> acts;
9940   const QList< QgsMasterLayoutInterface * > layouts = QgsProject::instance()->layoutManager()->layouts();
9941   acts.reserve( layouts.size() );
9942   for ( QgsMasterLayoutInterface *layout : layouts )
9943   {
9944     QAction *a = new QAction( layout->name(), menu );
9945     connect( a, &QAction::triggered, this, [this, layout]
9946     {
9947       openLayoutDesignerDialog( layout );
9948     } );
9949     acts << a;
9950   }
9951   if ( acts.size() > 1 )
9952   {
9953     // sort actions by text
9954     std::sort( acts.begin(), acts.end(), cmpByText_ );
9955   }
9956   menu->addActions( acts );
9957 }
9958 
showPinnedLabels(bool show)9959 void QgisApp::showPinnedLabels( bool show )
9960 {
9961   mMapTools->mapTool< QgsMapToolPinLabels >( QgsAppMapTools::PinLabels )->showPinnedLabels( show );
9962 }
9963 
pinLabels()9964 void QgisApp::pinLabels()
9965 {
9966   mActionShowPinnedLabels->setChecked( true );
9967   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::PinLabels ) );
9968 }
9969 
showHideLabels()9970 void QgisApp::showHideLabels()
9971 {
9972   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::ShowHideLabels ) );
9973 }
9974 
moveLabel()9975 void QgisApp::moveLabel()
9976 {
9977   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::MoveLabel ) );
9978 }
9979 
rotateFeature()9980 void QgisApp::rotateFeature()
9981 {
9982   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::RotateFeature ) );
9983 }
9984 
scaleFeature()9985 void QgisApp::scaleFeature()
9986 {
9987   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::ScaleFeature ) );
9988 }
9989 
rotateLabel()9990 void QgisApp::rotateLabel()
9991 {
9992   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::RotateLabel ) );
9993 }
9994 
changeLabelProperties()9995 void QgisApp::changeLabelProperties()
9996 {
9997   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::ChangeLabelProperties ) );
9998 }
9999 
annotationItems()10000 QList<QgsMapCanvasAnnotationItem *> QgisApp::annotationItems()
10001 {
10002   QList<QgsMapCanvasAnnotationItem *> itemList;
10003 
10004   if ( !mMapCanvas )
10005   {
10006     return itemList;
10007   }
10008 
10009   if ( mMapCanvas )
10010   {
10011     QList<QGraphicsItem *> graphicsItems = mMapCanvas->items();
10012     QList<QGraphicsItem *>::iterator gIt = graphicsItems.begin();
10013     for ( ; gIt != graphicsItems.end(); ++gIt )
10014     {
10015       QgsMapCanvasAnnotationItem *currentItem = dynamic_cast<QgsMapCanvasAnnotationItem *>( *gIt );
10016       if ( currentItem )
10017       {
10018         itemList.push_back( currentItem );
10019       }
10020     }
10021   }
10022   return itemList;
10023 }
10024 
mapCanvases()10025 QList<QgsMapCanvas *> QgisApp::mapCanvases()
10026 {
10027   // filter out browser canvases -- they are children of app, but a different
10028   // kind of beast, and here we only want the main canvas or dock canvases
10029   auto canvases = findChildren< QgsMapCanvas * >();
10030   canvases.erase( std::remove_if( canvases.begin(), canvases.end(),
10031                                   []( QgsMapCanvas * canvas )
10032   {
10033     return !canvas || canvas->property( "browser_canvas" ).toBool();
10034   } ), canvases.end() );
10035   return canvases;
10036 }
10037 
removeAnnotationItems()10038 void QgisApp::removeAnnotationItems()
10039 {
10040   if ( !mMapCanvas )
10041   {
10042     return;
10043   }
10044   QGraphicsScene *scene = mMapCanvas->scene();
10045   if ( !scene )
10046   {
10047     return;
10048   }
10049   QList<QgsMapCanvasAnnotationItem *> itemList = annotationItems();
10050   const auto constItemList = itemList;
10051   for ( QgsMapCanvasAnnotationItem *item : constItemList )
10052   {
10053     if ( item )
10054     {
10055       scene->removeItem( item );
10056       delete item;
10057     }
10058   }
10059 }
10060 
mergeAttributesOfSelectedFeatures()10061 void QgisApp::mergeAttributesOfSelectedFeatures()
10062 {
10063   //get active layer (hopefully vector)
10064   QgsMapLayer *activeMapLayer = activeLayer();
10065   if ( !activeMapLayer )
10066   {
10067     visibleMessageBar()->pushMessage( tr( "No active layer" ),
10068                                       tr( "No active layer found. Please select a layer in the layer list" ),
10069                                       Qgis::MessageLevel::Info );
10070     return;
10071   }
10072 
10073   QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( activeMapLayer );
10074   if ( !vl )
10075   {
10076     visibleMessageBar()->pushMessage(
10077       tr( "Layer not editable" ),
10078       tr( "The merge features tool only works on vector layers." ),
10079       Qgis::MessageLevel::Warning );
10080     return;
10081   }
10082 
10083   if ( !vl->isEditable() )
10084   {
10085     visibleMessageBar()->pushMessage(
10086       tr( "Layer not editable" ),
10087       tr( "Merging features can only be done for layers in editing mode." ),
10088       Qgis::MessageLevel::Warning );
10089 
10090     return;
10091   }
10092 
10093   //get selected feature ids (as a QSet<int> )
10094   const QgsFeatureIds &featureIdSet = vl->selectedFeatureIds();
10095   if ( featureIdSet.size() < 2 )
10096   {
10097     visibleMessageBar()->pushMessage(
10098       tr( "Not enough features selected" ),
10099       tr( "The merge tool requires at least two selected features." ),
10100       Qgis::MessageLevel::Warning );
10101     return;
10102   }
10103 
10104   //get initial selection (may be altered by attribute merge dialog later)
10105   QgsFeatureList featureList = vl->selectedFeatures();
10106 
10107   //merge the attributes together
10108   QgsMergeAttributesDialog d( featureList, vl, mapCanvas() );
10109   //initialize dialog with all columns set to skip
10110   d.setAllToSkip();
10111   if ( d.exec() == QDialog::Rejected )
10112   {
10113     return;
10114   }
10115 
10116   vl->beginEditCommand( tr( "Merged feature attributes" ) );
10117 
10118   QgsAttributes merged = d.mergedAttributes();
10119   QSet<int> toSkip = d.skippedAttributeIndexes();
10120 
10121   bool firstFeature = true;
10122   const auto constSelectedFeatureIds = vl->selectedFeatureIds();
10123   for ( QgsFeatureId fid : constSelectedFeatureIds )
10124   {
10125     for ( int i = 0; i < merged.count(); ++i )
10126     {
10127       if ( toSkip.contains( i ) )
10128         continue;
10129 
10130       QVariant val = merged.at( i );
10131       QgsField fld( vl->fields().at( i ) );
10132       bool isDefaultValue = vl->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
10133                             vl->dataProvider() &&
10134                             vl->dataProvider()->defaultValueClause( vl->fields().fieldOriginIndex( i ) ) == val;
10135 
10136       // convert to destination data type
10137       QString errorMessage;
10138       if ( !isDefaultValue && !fld.convertCompatible( val, &errorMessage ) )
10139       {
10140         if ( firstFeature )
10141         {
10142           //only warn on first feature
10143           visibleMessageBar()->pushMessage(
10144             tr( "Invalid result" ),
10145             tr( "Could not store value '%1' in field of type %2: %3" ).arg( merged.at( i ).toString(), fld.typeName(), errorMessage ),
10146             Qgis::MessageLevel::Warning );
10147         }
10148       }
10149       else
10150       {
10151         vl->changeAttributeValue( fid, i, val );
10152       }
10153     }
10154     firstFeature = false;
10155   }
10156 
10157   vl->endEditCommand();
10158 
10159   vl->triggerRepaint();
10160 }
10161 
modifyAttributesOfSelectedFeatures()10162 void QgisApp::modifyAttributesOfSelectedFeatures()
10163 {
10164   QgsMapLayer *activeMapLayer = activeLayer();
10165   if ( !activeMapLayer )
10166   {
10167     visibleMessageBar()->pushMessage(
10168       tr( "No active layer" ),
10169       tr( "Please select a layer in the layer list" ),
10170       Qgis::MessageLevel::Warning );
10171     return;
10172   }
10173 
10174   QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( activeMapLayer );
10175   if ( !vl )
10176   {
10177     visibleMessageBar()->pushMessage(
10178       tr( "Invalid layer" ),
10179       tr( "The merge features tool only works on vector layers." ),
10180       Qgis::MessageLevel::Warning );
10181     return;
10182   }
10183   if ( !vl->isEditable() )
10184   {
10185     visibleMessageBar()->pushMessage(
10186       tr( "Layer not editable" ),
10187       tr( "Modifying features can only be done for layers in editing mode." ),
10188       Qgis::MessageLevel::Warning );
10189 
10190     return;
10191   }
10192 
10193   QgsAttributeEditorContext context( createAttributeEditorContext() );
10194   context.setAllowCustomUi( false );
10195   context.setVectorLayerTools( mVectorLayerTools );
10196   context.setCadDockWidget( mAdvancedDigitizingDockWidget );
10197   context.setMapCanvas( mMapCanvas );
10198 
10199   QgsAttributeDialog *dialog = nullptr;
10200   if ( vl->selectedFeatureCount() == 1 )
10201   {
10202     context.setAttributeFormMode( QgsAttributeEditorContext::Mode::SingleEditMode );
10203     QgsFeature f = vl->selectedFeatures().at( 0 );
10204     dialog = new QgsAttributeDialog( vl, &f, false, this, true, context );
10205     dialog->setMode( QgsAttributeEditorContext::SingleEditMode );
10206   }
10207   else
10208   {
10209     context.setAttributeFormMode( QgsAttributeEditorContext::Mode::MultiEditMode );
10210 
10211     //dummy feature
10212     QgsFeature f( vl->fields() );
10213     dialog = new QgsAttributeDialog( vl, &f, false, this, true, context );
10214     dialog->setMode( QgsAttributeEditorContext::MultiEditMode );
10215   }
10216   dialog->setAttribute( Qt::WA_DeleteOnClose );
10217   dialog->show();
10218 }
10219 
mergeSelectedFeatures()10220 void QgisApp::mergeSelectedFeatures()
10221 {
10222   //get active layer (hopefully vector)
10223   QgsMapLayer *activeMapLayer = activeLayer();
10224   if ( !activeMapLayer )
10225   {
10226     visibleMessageBar()->pushMessage(
10227       tr( "No active layer" ),
10228       tr( "Please select a layer in the layer list" ),
10229       Qgis::MessageLevel::Warning );
10230     return;
10231   }
10232   QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( activeMapLayer );
10233   if ( !vl )
10234   {
10235     visibleMessageBar()->pushMessage(
10236       tr( "Invalid layer" ),
10237       tr( "The merge features tool only works on vector layers." ),
10238       Qgis::MessageLevel::Warning );
10239     return;
10240   }
10241   if ( !vl->isEditable() )
10242   {
10243     visibleMessageBar()->pushMessage(
10244       tr( "Layer not editable" ),
10245       tr( "Merging features can only be done for layers in editing mode." ),
10246       Qgis::MessageLevel::Warning );
10247 
10248     return;
10249   }
10250 
10251   //get selected feature ids (as a QSet<int> )
10252   const QgsFeatureIds &featureIdSet = vl->selectedFeatureIds();
10253   if ( featureIdSet.size() < 2 )
10254   {
10255     visibleMessageBar()->pushMessage(
10256       tr( "Not enough features selected" ),
10257       tr( "The merge tool requires at least two selected features" ),
10258       Qgis::MessageLevel::Warning );
10259     return;
10260   }
10261 
10262   //get initial selection (may be altered by attribute merge dialog later)
10263   QgsFeatureIds featureIds = vl->selectedFeatureIds();
10264   QgsFeatureList featureList = vl->selectedFeatures();
10265   bool canceled;
10266   QgsGeometry unionGeom = unionGeometries( vl, featureList, canceled );
10267   if ( unionGeom.isNull() )
10268   {
10269     if ( !canceled )
10270     {
10271       visibleMessageBar()->pushMessage(
10272         tr( "Merge failed" ),
10273         tr( "An error occurred during the merge operation." ),
10274         Qgis::MessageLevel::Critical );
10275     }
10276     return;
10277   }
10278 
10279   //merge the attributes together
10280   QgsMergeAttributesDialog d( featureList, vl, mapCanvas() );
10281   d.setWindowTitle( tr( "Merge Features" ) );
10282   if ( d.exec() == QDialog::Rejected )
10283   {
10284     return;
10285   }
10286 
10287   QgsFeatureIds featureIdsAfter = vl->selectedFeatureIds();
10288 
10289   if ( featureIdsAfter.size() < 2 )
10290   {
10291     visibleMessageBar()->pushMessage(
10292       tr( "Not enough features selected" ),
10293       tr( "The merge tool requires at least two selected features" ),
10294       Qgis::MessageLevel::Warning );
10295     return;
10296   }
10297 
10298   //if the user changed the feature selection in the merge dialog, we need to repeat the union and check the type
10299   if ( featureIds.size() != featureIdsAfter.size() )
10300   {
10301     bool canceled;
10302     QgsFeatureList featureListAfter = vl->selectedFeatures();
10303     unionGeom = unionGeometries( vl, featureListAfter, canceled );
10304     if ( unionGeom.isNull() )
10305     {
10306       if ( !canceled )
10307       {
10308         visibleMessageBar()->pushMessage(
10309           tr( "Merge failed" ),
10310           tr( "An error occurred during the merge operation." ),
10311           Qgis::MessageLevel::Critical );
10312       }
10313       return;
10314     }
10315   }
10316 
10317   QgsAttributes attrs = d.mergedAttributes();
10318   QgsAttributeMap newAttributes;
10319   QString errorMessage;
10320   for ( int i = 0; i < attrs.count(); ++i )
10321   {
10322     QVariant val = attrs.at( i );
10323     bool isDefaultValue = vl->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
10324                           vl->dataProvider() &&
10325                           vl->dataProvider()->defaultValueClause( vl->fields().fieldOriginIndex( i ) ) == val;
10326 
10327     // convert to destination data type
10328     if ( !isDefaultValue && !vl->fields().at( i ).convertCompatible( val, &errorMessage ) )
10329     {
10330       visibleMessageBar()->pushMessage(
10331         tr( "Invalid result" ),
10332         tr( "Could not store value '%1' in field of type %2: %3" ).arg( attrs.at( i ).toString(), vl->fields().at( i ).typeName(), errorMessage ),
10333         Qgis::MessageLevel::Warning );
10334     }
10335     newAttributes[ i ] = val;
10336   }
10337 
10338   vl->beginEditCommand( tr( "Merged features" ) );
10339 
10340   //create new feature
10341   QgsFeature newFeature = QgsVectorLayerUtils::createFeature( vl, unionGeom, newAttributes );
10342 
10343   QgsFeatureIds::const_iterator feature_it = featureIdsAfter.constBegin();
10344   for ( ; feature_it != featureIdsAfter.constEnd(); ++feature_it )
10345   {
10346     vl->deleteFeature( *feature_it );
10347   }
10348 
10349   vl->addFeature( newFeature );
10350 
10351   vl->endEditCommand();
10352 
10353   vl->triggerRepaint();
10354 }
10355 
vertexTool()10356 void QgisApp::vertexTool()
10357 {
10358   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::VertexTool ) );
10359 }
10360 
vertexToolActiveLayer()10361 void QgisApp::vertexToolActiveLayer()
10362 {
10363   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::VertexToolActiveLayer ) );
10364 }
10365 
rotatePointSymbols()10366 void QgisApp::rotatePointSymbols()
10367 {
10368   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::RotatePointSymbolsTool ) );
10369 }
10370 
offsetPointSymbol()10371 void QgisApp::offsetPointSymbol()
10372 {
10373   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::OffsetPointSymbolTool ) );
10374 }
10375 
snappingOptions()10376 void QgisApp::snappingOptions()
10377 {
10378   mSnappingDialogContainer->show();
10379 }
10380 
enableDigitizeWithCurve(bool enable)10381 void QgisApp::enableDigitizeWithCurve( bool enable )
10382 {
10383   if ( enable && mActionStreamDigitize->isChecked() )
10384   {
10385     mActionStreamDigitize->setChecked( false );
10386     enableStreamDigitizing( false );
10387   }
10388 
10389   if ( enable )
10390   {
10391     mDigitizeModeToolButton->setDefaultAction( mActionDigitizeWithCurve );
10392     QgsSettings().setValue( QStringLiteral( "UI/digitizeTechnique" ), 0 );
10393   }
10394 
10395   const QList< QgsMapToolCapture * > tools = captureTools();
10396   for ( QgsMapToolCapture *tool : tools )
10397   {
10398     if ( tool->supportsTechnique( QgsMapToolCapture::CircularString ) )
10399       tool->setCircularDigitizingEnabled( enable );
10400   }
10401   QgsSettings settings;
10402   settings.setValue( QStringLiteral( "UI/digitizeWithCurve" ), enable ? 1 : 0 );
10403 }
10404 
enableStreamDigitizing(bool enable)10405 void QgisApp::enableStreamDigitizing( bool enable )
10406 {
10407   if ( enable && mActionDigitizeWithCurve->isChecked() )
10408   {
10409     mActionDigitizeWithCurve->setChecked( false );
10410     enableDigitizeWithCurve( false );
10411   }
10412 
10413   if ( enable )
10414   {
10415     mDigitizeModeToolButton->setDefaultAction( mActionStreamDigitize );
10416     QgsSettings().setValue( QStringLiteral( "UI/digitizeTechnique" ), 1 );
10417   }
10418 
10419   const QList< QgsMapToolCapture * > tools = captureTools();
10420   for ( QgsMapToolCapture *tool : tools )
10421   {
10422     if ( tool->supportsTechnique( QgsMapToolCapture::Streaming ) )
10423       tool->setStreamDigitizingEnabled( enable );
10424   }
10425   QgsSettings settings;
10426   settings.setValue( QStringLiteral( "UI/digitizeWithStream" ), enable ? 1 : 0 );
10427 }
10428 
enableDigitizeTechniqueActions(bool enable,QAction * triggeredFromToolAction)10429 void QgisApp::enableDigitizeTechniqueActions( bool enable, QAction *triggeredFromToolAction )
10430 {
10431   if ( !mMapTools )
10432     return;
10433 
10434   QgsSettings settings;
10435 
10436   const QList< QgsMapToolCapture * > tools = captureTools();
10437 
10438   QSet< QgsMapToolCapture::CaptureTechnique > supportedTechniques;
10439   for ( QgsMapToolCapture *tool : tools )
10440   {
10441     if ( triggeredFromToolAction == tool->action() || ( !triggeredFromToolAction && mMapCanvas->mapTool() == tool ) )
10442     {
10443       for ( QgsMapToolCapture::CaptureTechnique technique : { QgsMapToolCapture::CircularString, QgsMapToolCapture::Streaming } )
10444       {
10445         if ( tool->supportsTechnique( technique ) )
10446           supportedTechniques.insert( technique );
10447       }
10448       break;
10449     }
10450   }
10451 
10452   mActionDigitizeWithCurve->setEnabled( enable && supportedTechniques.contains( QgsMapToolCapture::CircularString ) );
10453   const bool curveIsChecked = settings.value( QStringLiteral( "UI/digitizeWithCurve" ) ).toInt();
10454   mActionDigitizeWithCurve->setChecked( curveIsChecked && mActionDigitizeWithCurve->isEnabled() );
10455 
10456   mActionStreamDigitize->setEnabled( enable && supportedTechniques.contains( QgsMapToolCapture::Streaming ) );
10457   const bool streamIsChecked = settings.value( QStringLiteral( "UI/digitizeWithStream" ) ).toInt();
10458   mActionStreamDigitize->setChecked( streamIsChecked && mActionStreamDigitize->isEnabled() );
10459 
10460   for ( QgsMapToolCapture *tool : tools )
10461   {
10462     if ( tool->supportsTechnique( QgsMapToolCapture::CircularString ) )
10463       tool->setCircularDigitizingEnabled( mActionDigitizeWithCurve->isChecked() );
10464     if ( tool->supportsTechnique( QgsMapToolCapture::Streaming ) )
10465       tool->setStreamDigitizingEnabled( mActionStreamDigitize->isChecked() );
10466   }
10467 }
10468 
splitFeatures()10469 void QgisApp::splitFeatures()
10470 {
10471   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::SplitFeatures ) );
10472 }
10473 
splitParts()10474 void QgisApp::splitParts()
10475 {
10476   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::SplitParts ) );
10477 }
10478 
reshapeFeatures()10479 void QgisApp::reshapeFeatures()
10480 {
10481   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::ReshapeFeatures ) );
10482 }
10483 
addFeature()10484 void QgisApp::addFeature()
10485 {
10486   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::AddFeature ) );
10487 }
10488 
setMapTool(QgsMapTool * tool,bool clean)10489 void QgisApp::setMapTool( QgsMapTool *tool, bool clean )
10490 {
10491   mMapCanvas->setMapTool( tool, clean );
10492 }
10493 
selectFeatures()10494 void QgisApp::selectFeatures()
10495 {
10496   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::SelectFeatures ) );
10497 }
10498 
selectByPolygon()10499 void QgisApp::selectByPolygon()
10500 {
10501   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::SelectPolygon ) );
10502 }
10503 
selectByFreehand()10504 void QgisApp::selectByFreehand()
10505 {
10506   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::SelectFreehand ) );
10507 }
10508 
selectByRadius()10509 void QgisApp::selectByRadius()
10510 {
10511   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::SelectRadius ) );
10512 }
10513 
deselectAll()10514 void QgisApp::deselectAll()
10515 {
10516   // Turn off rendering to improve speed.
10517   QgsCanvasRefreshBlocker refreshBlocker;
10518 
10519   QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
10520   for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
10521   {
10522     QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
10523     if ( !vl )
10524       continue;
10525 
10526     vl->removeSelection();
10527   }
10528 }
10529 
deselectActiveLayer()10530 void QgisApp::deselectActiveLayer()
10531 {
10532   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
10533 
10534   if ( !vlayer )
10535   {
10536     visibleMessageBar()->pushMessage(
10537       tr( "No active vector layer" ),
10538       tr( "To deselect all features, choose a vector layer in the legend" ),
10539       Qgis::MessageLevel::Info );
10540     return;
10541   }
10542 
10543   vlayer->removeSelection();
10544 }
10545 
invertSelection()10546 void QgisApp::invertSelection()
10547 {
10548   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
10549   if ( !vlayer )
10550   {
10551     visibleMessageBar()->pushMessage(
10552       tr( "No active vector layer" ),
10553       tr( "To invert selection, choose a vector layer in the legend" ),
10554       Qgis::MessageLevel::Info );
10555     return;
10556   }
10557 
10558   vlayer->invertSelection();
10559 }
10560 
selectAll()10561 void QgisApp::selectAll()
10562 {
10563   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
10564   if ( !vlayer )
10565   {
10566     visibleMessageBar()->pushMessage(
10567       tr( "No active vector layer" ),
10568       tr( "To select all, choose a vector layer in the legend." ),
10569       Qgis::MessageLevel::Info );
10570     return;
10571   }
10572 
10573   vlayer->selectAll();
10574 }
10575 
selectByExpression()10576 void QgisApp::selectByExpression()
10577 {
10578   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
10579   if ( !vlayer )
10580   {
10581     visibleMessageBar()->pushMessage(
10582       tr( "No active vector layer" ),
10583       tr( "To select features, choose a vector layer in the legend." ),
10584       Qgis::MessageLevel::Info );
10585     return;
10586   }
10587 
10588   QgsExpressionSelectionDialog *dlg = new QgsExpressionSelectionDialog( vlayer, QString(), this );
10589   dlg->setMessageBar( messageBar() );
10590   dlg->setMapCanvas( mapCanvas() );
10591   dlg->setAttribute( Qt::WA_DeleteOnClose );
10592   dlg->show();
10593 }
10594 
selectByForm()10595 void QgisApp::selectByForm()
10596 {
10597   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
10598   if ( !vlayer )
10599   {
10600     visibleMessageBar()->pushMessage(
10601       tr( "No active vector layer" ),
10602       tr( "To select features, choose a vector layer in the legend." ),
10603       Qgis::MessageLevel::Info );
10604     return;
10605   }
10606   QgsDistanceArea myDa;
10607 
10608   myDa.setSourceCrs( vlayer->crs(), QgsProject::instance()->transformContext() );
10609   myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
10610 
10611   QgsAttributeEditorContext context;
10612   context.setDistanceArea( myDa );
10613   context.setVectorLayerTools( mVectorLayerTools );
10614   context.setCadDockWidget( mAdvancedDigitizingDockWidget );
10615   context.setMapCanvas( mMapCanvas );
10616 
10617   QgsSelectByFormDialog *dlg = new QgsSelectByFormDialog( vlayer, context, this );
10618   dlg->setMessageBar( messageBar() );
10619   dlg->setMapCanvas( mapCanvas() );
10620   dlg->setAttribute( Qt::WA_DeleteOnClose );
10621   dlg->show();
10622 }
10623 
addRing()10624 void QgisApp::addRing()
10625 {
10626   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::AddRing ) );
10627 }
10628 
fillRing()10629 void QgisApp::fillRing()
10630 {
10631   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::FillRing ) );
10632 }
10633 
10634 
addPart()10635 void QgisApp::addPart()
10636 {
10637   mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::AddPart ) );
10638 }
10639 
10640 
cutSelectionToClipboard(QgsMapLayer * layerContainingSelection)10641 void QgisApp::cutSelectionToClipboard( QgsMapLayer *layerContainingSelection )
10642 {
10643   // Test for feature support in this layer
10644   QgsVectorLayer *selectionVectorLayer = qobject_cast<QgsVectorLayer *>( layerContainingSelection ? layerContainingSelection : activeLayer() );
10645   if ( !selectionVectorLayer )
10646     return;
10647 
10648   if ( !selectionVectorLayer->isEditable() )
10649   {
10650     visibleMessageBar()->pushMessage( tr( "Layer not editable" ),
10651                                       tr( "The current layer is not editable. Choose 'Start editing' in the digitizing toolbar." ),
10652                                       Qgis::MessageLevel::Info );
10653     return;
10654   }
10655 
10656   clipboard()->replaceWithCopyOf( selectionVectorLayer );
10657 
10658   selectionVectorLayer->beginEditCommand( tr( "Features cut" ) );
10659   selectionVectorLayer->deleteSelectedFeatures();
10660   selectionVectorLayer->endEditCommand();
10661 }
10662 
copySelectionToClipboard(QgsMapLayer * layerContainingSelection)10663 void QgisApp::copySelectionToClipboard( QgsMapLayer *layerContainingSelection )
10664 {
10665   QgsVectorLayer *selectionVectorLayer = qobject_cast<QgsVectorLayer *>( layerContainingSelection ? layerContainingSelection : activeLayer() );
10666   if ( !selectionVectorLayer )
10667     return;
10668 
10669   // Test for feature support in this layer
10670   clipboard()->replaceWithCopyOf( selectionVectorLayer );
10671 }
10672 
clipboardChanged()10673 void QgisApp::clipboardChanged()
10674 {
10675   activateDeactivateLayerRelatedActions( activeLayer() );
10676 }
10677 
pasteFromClipboard(QgsMapLayer * destinationLayer)10678 void QgisApp::pasteFromClipboard( QgsMapLayer *destinationLayer )
10679 {
10680   QgsVectorLayer *pasteVectorLayer = qobject_cast<QgsVectorLayer *>( destinationLayer ? destinationLayer : activeLayer() );
10681   if ( !pasteVectorLayer )
10682     return;
10683 
10684   if ( !pasteVectorLayer->isEditable() )
10685   {
10686     visibleMessageBar()->pushMessage( tr( "Layer not editable" ),
10687                                       tr( "The current layer is not editable. Choose 'Start editing' in the digitizing toolbar." ),
10688                                       Qgis::MessageLevel::Info );
10689     return;
10690   }
10691 
10692   pasteVectorLayer->beginEditCommand( tr( "Features pasted" ) );
10693   QgsFeatureList features = clipboard()->transformedCopyOf( pasteVectorLayer->crs(), pasteVectorLayer->fields() );
10694   int nTotalFeatures = features.count();
10695   QgsExpressionContext context = pasteVectorLayer->createExpressionContext();
10696 
10697   QgsFeatureList compatibleFeatures( QgsVectorLayerUtils::makeFeaturesCompatible( features, pasteVectorLayer, QgsFeatureSink::RegeneratePrimaryKey ) );
10698   QgsVectorLayerUtils::QgsFeaturesDataList newFeaturesDataList;
10699   newFeaturesDataList.reserve( compatibleFeatures.size() );
10700 
10701   // Count collapsed geometries
10702   int invalidGeometriesCount = 0;
10703 
10704   for ( const auto &feature : std::as_const( compatibleFeatures ) )
10705   {
10706 
10707     QgsGeometry geom = feature.geometry();
10708 
10709     if ( !( geom.isEmpty() || geom.isNull( ) ) )
10710     {
10711       // avoid intersection if enabled in digitize settings
10712       QList<QgsVectorLayer *>  avoidIntersectionsLayers;
10713       switch ( QgsProject::instance()->avoidIntersectionsMode() )
10714       {
10715         case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsCurrentLayer:
10716           avoidIntersectionsLayers.append( pasteVectorLayer );
10717           break;
10718         case QgsProject::AvoidIntersectionsMode::AvoidIntersectionsLayers:
10719           avoidIntersectionsLayers = QgsProject::instance()->avoidIntersectionsLayers();
10720           break;
10721         case QgsProject::AvoidIntersectionsMode::AllowIntersections:
10722           break;
10723       }
10724       if ( avoidIntersectionsLayers.size() > 0 )
10725       {
10726         geom.avoidIntersections( avoidIntersectionsLayers );
10727       }
10728 
10729       // count collapsed geometries
10730       if ( geom.isEmpty() || geom.isNull( ) )
10731         invalidGeometriesCount++;
10732     }
10733 
10734     QgsAttributeMap attrMap;
10735     for ( int i = 0; i < feature.attributes().count(); i++ )
10736     {
10737       attrMap[i] = feature.attribute( i );
10738     }
10739     newFeaturesDataList << QgsVectorLayerUtils::QgsFeatureData( geom, attrMap );
10740   }
10741 
10742   // now create new feature using pasted feature as a template. This automatically handles default
10743   // values and field constraints
10744   QgsFeatureList newFeatures {QgsVectorLayerUtils::createFeatures( pasteVectorLayer, newFeaturesDataList, &context )};
10745 
10746   // check constraints
10747   bool hasStrongConstraints = false;
10748 
10749   for ( const QgsField &field : pasteVectorLayer->fields() )
10750   {
10751     if ( ( field.constraints().constraints() & QgsFieldConstraints::ConstraintUnique && field.constraints().constraintStrength( QgsFieldConstraints::ConstraintUnique ) & QgsFieldConstraints::ConstraintStrengthHard )
10752          || ( field.constraints().constraints() & QgsFieldConstraints::ConstraintNotNull && field.constraints().constraintStrength( QgsFieldConstraints::ConstraintNotNull ) & QgsFieldConstraints::ConstraintStrengthHard )
10753          || ( field.constraints().constraints() & QgsFieldConstraints::ConstraintExpression && !field.constraints().constraintExpression().isEmpty() && field.constraints().constraintStrength( QgsFieldConstraints::ConstraintExpression ) & QgsFieldConstraints::ConstraintStrengthHard )
10754        )
10755     {
10756       hasStrongConstraints = true;
10757       break;
10758     }
10759   }
10760 
10761   if ( hasStrongConstraints )
10762   {
10763     QgsFeatureList validFeatures = newFeatures;
10764     QgsFeatureList invalidFeatures;
10765     QMutableListIterator<QgsFeature> it( validFeatures );
10766     while ( it.hasNext() )
10767     {
10768       QgsFeature &f = it.next();
10769       for ( int idx = 0; idx < pasteVectorLayer->fields().count(); ++idx )
10770       {
10771         QStringList errors;
10772         if ( !QgsVectorLayerUtils::validateAttribute( pasteVectorLayer, f, idx, errors, QgsFieldConstraints::ConstraintStrengthHard, QgsFieldConstraints::ConstraintOriginNotSet ) )
10773         {
10774           invalidFeatures << f;
10775           it.remove();
10776           break;
10777         }
10778       }
10779     }
10780 
10781     if ( !invalidFeatures.isEmpty() )
10782     {
10783       newFeatures.clear();
10784 
10785       QgsAttributeEditorContext context( createAttributeEditorContext() );
10786       context.setAllowCustomUi( false );
10787       context.setFormMode( QgsAttributeEditorContext::StandaloneDialog );
10788 
10789       QgsFixAttributeDialog *dialog = new QgsFixAttributeDialog( pasteVectorLayer, invalidFeatures, this, context );
10790 
10791       connect( dialog, &QgsFixAttributeDialog::finished, this, [ = ]( int feedback )
10792       {
10793         QgsFeatureList features = newFeatures;
10794         switch ( feedback )
10795         {
10796           case QgsFixAttributeDialog::PasteValid:
10797             //paste valid and fixed, vanish unfixed
10798             features << validFeatures << dialog->fixedFeatures();
10799             break;
10800           case QgsFixAttributeDialog::PasteAll:
10801             //paste all, even unfixed
10802             features << validFeatures << dialog->fixedFeatures() << dialog->unfixedFeatures();
10803             break;
10804         }
10805         pasteFeatures( pasteVectorLayer, invalidGeometriesCount, nTotalFeatures, features );
10806         dialog->deleteLater();
10807       } );
10808       dialog->show();
10809       return;
10810     }
10811   }
10812 
10813   pasteFeatures( pasteVectorLayer, invalidGeometriesCount, nTotalFeatures, newFeatures );
10814 }
10815 
pasteFeatures(QgsVectorLayer * pasteVectorLayer,int invalidGeometriesCount,int nTotalFeatures,QgsFeatureList & features)10816 void QgisApp::pasteFeatures( QgsVectorLayer *pasteVectorLayer, int invalidGeometriesCount, int nTotalFeatures, QgsFeatureList &features )
10817 {
10818   int nCopiedFeatures = features.count();
10819   if ( pasteVectorLayer->addFeatures( features ) )
10820   {
10821     QgsFeatureIds newIds;
10822     newIds.reserve( features.size() );
10823     for ( const QgsFeature &f : std::as_const( features ) )
10824     {
10825       newIds << f.id();
10826     }
10827 
10828     pasteVectorLayer->selectByIds( newIds );
10829   }
10830   else
10831   {
10832     nCopiedFeatures = 0;
10833   }
10834   pasteVectorLayer->endEditCommand();
10835   pasteVectorLayer->updateExtents();
10836 
10837   Qgis::MessageLevel level = ( nCopiedFeatures == 0 || invalidGeometriesCount > 0 ) ? Qgis::MessageLevel::Warning : Qgis::MessageLevel::Info;
10838   QString message;
10839   if ( nCopiedFeatures == 0 )
10840   {
10841     message = tr( "No features pasted." );
10842   }
10843   else if ( nCopiedFeatures == nTotalFeatures )
10844   {
10845     message = tr( "%1 features were pasted." ).arg( nCopiedFeatures );
10846   }
10847   else
10848   {
10849     message = tr( "%1 of %2 features could be pasted." ).arg( nCopiedFeatures ).arg( nTotalFeatures );
10850   }
10851 
10852   // warn the user if the pasted features have invalid geometries
10853   if ( invalidGeometriesCount > 0 )
10854     message +=  invalidGeometriesCount == 1 ? tr( " Geometry collapsed due to intersection avoidance." ) :
10855                 tr( "%1 geometries collapsed due to intersection avoidance." )
10856                 .arg( invalidGeometriesCount );
10857 
10858   visibleMessageBar()->pushMessage( tr( "Paste features" ),
10859                                     message,
10860                                     level );
10861 
10862   pasteVectorLayer->triggerRepaint();
10863 }
10864 
pasteAsNewVector()10865 void QgisApp::pasteAsNewVector()
10866 {
10867 
10868   std::unique_ptr< QgsVectorLayer > layer = pasteToNewMemoryVector();
10869   if ( !layer )
10870     return;
10871 
10872   saveAsVectorFileGeneral( layer.get(), false );
10873 }
10874 
pasteAsNewMemoryVector(const QString & layerName)10875 QgsVectorLayer *QgisApp::pasteAsNewMemoryVector( const QString &layerName )
10876 {
10877   QString layerNameCopy = layerName;
10878 
10879   if ( layerNameCopy.isEmpty() )
10880   {
10881     bool ok;
10882     QString defaultName = tr( "Pasted" );
10883     layerNameCopy = QInputDialog::getText( this, tr( "Paste as Scratch Layer" ),
10884                                            tr( "Layer name" ), QLineEdit::Normal,
10885                                            defaultName, &ok );
10886     if ( !ok )
10887       return nullptr;
10888 
10889     if ( layerNameCopy.isEmpty() )
10890     {
10891       layerNameCopy = defaultName;
10892     }
10893   }
10894 
10895   std::unique_ptr< QgsVectorLayer > layer = pasteToNewMemoryVector();
10896   if ( !layer )
10897     return nullptr;
10898 
10899   layer->setName( layerNameCopy );
10900 
10901   QgsCanvasRefreshBlocker refreshBlocker;
10902 
10903   QgsVectorLayer *result = layer.get();
10904   QgsProject::instance()->addMapLayer( layer.release() );
10905 
10906   return result;
10907 }
10908 
pasteToNewMemoryVector()10909 std::unique_ptr<QgsVectorLayer> QgisApp::pasteToNewMemoryVector()
10910 {
10911   const QgsFields fields = clipboard()->fields();
10912 
10913   // Decide geometry type from features, switch to multi type if at least one multi is found
10914   QMap<QgsWkbTypes::Type, int> typeCounts;
10915   const QgsFeatureList features = clipboard()->copyOf( fields );
10916   for ( const QgsFeature &feature : features )
10917   {
10918     if ( !feature.hasGeometry() )
10919       continue;
10920 
10921     const QgsWkbTypes::Type type = feature.geometry().wkbType();
10922 
10923     if ( type == QgsWkbTypes::Unknown || type == QgsWkbTypes::NoGeometry )
10924       continue;
10925 
10926     if ( QgsWkbTypes::isSingleType( type ) )
10927     {
10928       if ( typeCounts.contains( QgsWkbTypes::multiType( type ) ) )
10929       {
10930         typeCounts[ QgsWkbTypes::multiType( type )] = typeCounts[ QgsWkbTypes::multiType( type )] + 1;
10931       }
10932       else
10933       {
10934         typeCounts[ type ] = typeCounts[ type ] + 1;
10935       }
10936     }
10937     else if ( QgsWkbTypes::isMultiType( type ) )
10938     {
10939       if ( typeCounts.contains( QgsWkbTypes::singleType( type ) ) )
10940       {
10941         // switch to multi type
10942         typeCounts[type] = typeCounts[ QgsWkbTypes::singleType( type )];
10943         typeCounts.remove( QgsWkbTypes::singleType( type ) );
10944       }
10945       typeCounts[type] = typeCounts[type] + 1;
10946     }
10947   }
10948 
10949   const QgsWkbTypes::Type wkbType = !typeCounts.isEmpty() ? typeCounts.keys().value( 0 ) : QgsWkbTypes::NoGeometry;
10950 
10951   if ( features.isEmpty() )
10952   {
10953     // should not happen
10954     visibleMessageBar()->pushMessage( tr( "Paste features" ),
10955                                       tr( "No features in clipboard." ),
10956                                       Qgis::MessageLevel::Info );
10957     return nullptr;
10958   }
10959   else if ( typeCounts.size() > 1 )
10960   {
10961     QString typeName = wkbType != QgsWkbTypes::NoGeometry ? QgsWkbTypes::displayString( wkbType ) : QStringLiteral( "none" );
10962     visibleMessageBar()->pushMessage( tr( "Paste features" ),
10963                                       tr( "Multiple geometry types found, features with geometry different from %1 will be created without geometry." ).arg( typeName ),
10964                                       Qgis::MessageLevel::Info );
10965   }
10966 
10967   std::unique_ptr< QgsVectorLayer > layer( QgsMemoryProviderUtils::createMemoryLayer( QStringLiteral( "pasted_features" ), QgsFields(), wkbType, clipboard()->crs() ) );
10968 
10969   if ( !layer->isValid() || !layer->dataProvider() )
10970   {
10971     visibleMessageBar()->pushMessage( tr( "Paste features" ),
10972                                       tr( "Cannot create new layer." ),
10973                                       Qgis::MessageLevel::Warning );
10974     return nullptr;
10975   }
10976 
10977   layer->startEditing();
10978   for ( const QgsField &f : clipboard()->fields() )
10979   {
10980     QgsDebugMsgLevel( QStringLiteral( "field %1 (%2)" ).arg( f.name(), QVariant::typeToName( f.type() ) ), 2 );
10981     if ( !layer->addAttribute( f ) )
10982     {
10983       visibleMessageBar()->pushMessage( tr( "Paste features" ),
10984                                         tr( "Cannot create field %1 (%2,%3)" ).arg( f.name(), f.typeName(), QVariant::typeToName( f.type() ) ),
10985                                         Qgis::MessageLevel::Warning );
10986       return nullptr;
10987     }
10988   }
10989 
10990   // Convert to multi if necessary
10991   QgsFeatureList convertedFeatures;
10992   convertedFeatures.reserve( features.length() );
10993   for ( QgsFeature feature : features )
10994   {
10995     if ( !feature.hasGeometry() )
10996     {
10997       convertedFeatures.append( feature );
10998       continue;
10999     }
11000 
11001     const QgsWkbTypes::Type type = feature.geometry().wkbType();
11002     if ( type == QgsWkbTypes::Unknown || type == QgsWkbTypes::NoGeometry )
11003     {
11004       convertedFeatures.append( feature );
11005       continue;
11006     }
11007 
11008     if ( QgsWkbTypes::singleType( wkbType ) != QgsWkbTypes::singleType( type ) )
11009     {
11010       feature.clearGeometry();
11011     }
11012 
11013     if ( QgsWkbTypes::isMultiType( wkbType ) &&  QgsWkbTypes::isSingleType( type ) )
11014     {
11015       QgsGeometry g = feature.geometry();
11016       g.convertToMultiType();
11017       feature.setGeometry( g );
11018     }
11019     convertedFeatures.append( feature );
11020   }
11021   if ( ! layer->addFeatures( convertedFeatures ) || !layer->commitChanges() )
11022   {
11023     QgsDebugMsg( QStringLiteral( "Cannot add features or commit changes" ) );
11024     return nullptr;
11025   }
11026 
11027   QgsDebugMsgLevel( QStringLiteral( "%1 features pasted to temporary scratch layer" ).arg( layer->featureCount() ), 2 );
11028   return layer;
11029 }
11030 
copyStyle(QgsMapLayer * sourceLayer,QgsMapLayer::StyleCategories categories)11031 void QgisApp::copyStyle( QgsMapLayer *sourceLayer, QgsMapLayer::StyleCategories categories )
11032 {
11033   QgsMapLayer *selectionLayer = sourceLayer ? sourceLayer : activeLayer();
11034 
11035   if ( selectionLayer )
11036   {
11037     QString errorMsg;
11038     QDomDocument doc( QStringLiteral( "qgis" ) );
11039     QgsReadWriteContext context;
11040     selectionLayer->exportNamedStyle( doc, errorMsg, context, categories );
11041 
11042     if ( !errorMsg.isEmpty() )
11043     {
11044       visibleMessageBar()->pushMessage( tr( "Cannot copy style" ),
11045                                         errorMsg,
11046                                         Qgis::MessageLevel::Critical );
11047       return;
11048     }
11049     // Copies data in text form as well, so the XML can be pasted into a text editor
11050     clipboard()->setData( QStringLiteral( QGSCLIPBOARD_STYLE_MIME ), doc.toByteArray(), doc.toString() );
11051 
11052     // Enables the paste menu element
11053     mActionPasteStyle->setEnabled( true );
11054   }
11055 }
11056 
pasteStyle(QgsMapLayer * destinationLayer,QgsMapLayer::StyleCategories categories)11057 void QgisApp::pasteStyle( QgsMapLayer *destinationLayer, QgsMapLayer::StyleCategories categories )
11058 {
11059   QgsMapLayer *selectionLayer = destinationLayer ? destinationLayer : activeLayer();
11060   if ( selectionLayer )
11061   {
11062     if ( clipboard()->hasFormat( QStringLiteral( QGSCLIPBOARD_STYLE_MIME ) ) )
11063     {
11064       QDomDocument doc( QStringLiteral( "qgis" ) );
11065       QString errorMsg;
11066       int errorLine, errorColumn;
11067       if ( !doc.setContent( clipboard()->data( QStringLiteral( QGSCLIPBOARD_STYLE_MIME ) ), false, &errorMsg, &errorLine, &errorColumn ) )
11068       {
11069 
11070         visibleMessageBar()->pushMessage( tr( "Cannot parse style" ),
11071                                           errorMsg,
11072                                           Qgis::MessageLevel::Critical );
11073         return;
11074       }
11075 
11076       bool isVectorStyle = doc.elementsByTagName( QStringLiteral( "pipe" ) ).isEmpty();
11077       if ( ( selectionLayer->type() == QgsMapLayerType::RasterLayer && isVectorStyle ) ||
11078            ( selectionLayer->type() == QgsMapLayerType::VectorLayer && !isVectorStyle ) )
11079       {
11080         return;
11081       }
11082 
11083       if ( !selectionLayer->importNamedStyle( doc, errorMsg, categories ) )
11084       {
11085         visibleMessageBar()->pushMessage( tr( "Cannot paste style" ),
11086                                           errorMsg,
11087                                           Qgis::MessageLevel::Critical );
11088         return;
11089       }
11090 
11091       mLayerTreeView->refreshLayerSymbology( selectionLayer->id() );
11092       selectionLayer->triggerRepaint();
11093     }
11094   }
11095 }
11096 
copyLayer()11097 void QgisApp::copyLayer()
11098 {
11099   QString errorMessage;
11100   QgsReadWriteContext readWriteContext;
11101   QDomDocument doc( QStringLiteral( "qgis-layer-definition" ) );
11102 
11103   bool saved = QgsLayerDefinition::exportLayerDefinition( doc, mLayerTreeView->selectedNodes(), errorMessage, readWriteContext );
11104 
11105   if ( !saved )
11106   {
11107     visibleMessageBar()->pushMessage( tr( "Error copying layer" ), errorMessage, Qgis::MessageLevel::Warning );
11108   }
11109 
11110   // Copies data in text form as well, so the XML can be pasted into a text editor
11111   clipboard()->setData( QStringLiteral( QGSCLIPBOARD_MAPLAYER_MIME ), doc.toByteArray(), doc.toString() );
11112   // Enables the paste menu element
11113   mActionPasteLayer->setEnabled( true );
11114 }
11115 
pasteLayer()11116 void QgisApp::pasteLayer()
11117 {
11118   if ( clipboard()->hasFormat( QStringLiteral( QGSCLIPBOARD_MAPLAYER_MIME ) ) )
11119   {
11120     QDomDocument doc;
11121     QString errorMessage;
11122     QgsReadWriteContext readWriteContext;
11123     doc.setContent( clipboard()->data( QStringLiteral( QGSCLIPBOARD_MAPLAYER_MIME ) ) );
11124 
11125     QgsLayerTreeNode *currentNode = mLayerTreeView->currentNode();
11126     QgsLayerTreeGroup *root = nullptr;
11127     if ( QgsLayerTree::isGroup( currentNode ) )
11128     {
11129       root = QgsLayerTree::toGroup( currentNode );
11130     }
11131     else
11132     {
11133       root = QgsProject::instance()->layerTreeRoot();
11134     }
11135 
11136     bool loaded = QgsLayerDefinition::loadLayerDefinition( doc, QgsProject::instance(), root,
11137                   errorMessage, readWriteContext );
11138 
11139     if ( !loaded || !errorMessage.isEmpty() )
11140     {
11141       visibleMessageBar()->pushMessage( tr( "Error pasting layer" ), errorMessage, Qgis::MessageLevel::Warning );
11142     }
11143   }
11144 }
11145 
copyFeatures(QgsFeatureStore & featureStore)11146 void QgisApp::copyFeatures( QgsFeatureStore &featureStore )
11147 {
11148   clipboard()->replaceWithCopyOf( featureStore );
11149 }
11150 
refreshMapCanvas(bool redrawAllLayers)11151 void QgisApp::refreshMapCanvas( bool redrawAllLayers )
11152 {
11153   const auto canvases = mapCanvases();
11154   for ( QgsMapCanvas *canvas : canvases )
11155   {
11156     //stop any current rendering
11157     canvas->stopRendering();
11158     if ( redrawAllLayers )
11159       canvas->refreshAllLayers();
11160     else
11161       canvas->refresh();
11162   }
11163 }
11164 
canvasRefreshStarted()11165 void QgisApp::canvasRefreshStarted()
11166 {
11167   mLastRenderTime.restart();
11168   // if previous render took less than 0.5 seconds, delay the appearance of the
11169   // render in progress status bar by 0.5 seconds - this avoids the status bar
11170   // rapidly appearing and then disappearing for very fast renders
11171   if ( mLastRenderTimeSeconds > 0 && mLastRenderTimeSeconds < 0.5 )
11172   {
11173     mRenderProgressBarTimer.setSingleShot( true );
11174     mRenderProgressBarTimer.setInterval( 500 );
11175     disconnect( mRenderProgressBarTimerConnection );
11176     mRenderProgressBarTimerConnection = connect( &mRenderProgressBarTimer, &QTimer::timeout, this, [ = ]()
11177     {
11178       showProgress( -1, 0 );
11179     }
11180                                                );
11181     mRenderProgressBarTimer.start();
11182   }
11183   else
11184   {
11185     showProgress( -1, 0 ); // trick to make progress bar show busy indicator
11186   }
11187 }
11188 
canvasRefreshFinished()11189 void QgisApp::canvasRefreshFinished()
11190 {
11191   mRenderProgressBarTimer.stop();
11192   mLastRenderTimeSeconds = mLastRenderTime.elapsed() / 1000.0;
11193   showProgress( 0, 0 ); // stop the busy indicator
11194 }
11195 
toggleMapTips(bool enabled)11196 void QgisApp::toggleMapTips( bool enabled )
11197 {
11198   mMapTipsVisible = enabled;
11199   // Store if maptips are active
11200   QgsSettings().setValue( QStringLiteral( "/qgis/enableMapTips" ), mMapTipsVisible );
11201 
11202   // if off, stop the timer
11203   if ( !mMapTipsVisible )
11204   {
11205     mpMapTipsTimer->stop();
11206     mpMaptip->clear( mMapCanvas );
11207   }
11208 
11209   if ( mActionMapTips->isChecked() != mMapTipsVisible )
11210     mActionMapTips->setChecked( mMapTipsVisible );
11211 }
11212 
toggleEditing()11213 void QgisApp::toggleEditing()
11214 {
11215   const QList<QgsMapLayer *> layerList = layerTreeView()->selectedLayers();
11216   if ( !layerList.isEmpty() )
11217   {
11218     // if there are selected layers, try to toggle those.
11219     // mActionToggleEditing has already been triggered at this point so its checked status has changed
11220     const bool shouldStartEditing = mActionToggleEditing->isChecked();
11221     for ( const auto layer : layerList )
11222     {
11223       if ( layer->supportsEditing() &&
11224            shouldStartEditing != layer->isEditable() )
11225       {
11226         toggleEditing( layer, true );
11227       }
11228     }
11229   }
11230   else
11231   {
11232     // if there are no selected layers, try to toggle the current layer
11233     QgsMapLayer *currentLayer =  activeLayer();
11234     if ( currentLayer && currentLayer->supportsEditing() )
11235     {
11236       toggleEditing( currentLayer, true );
11237     }
11238     else
11239     {
11240       // active although there's no layer active!?
11241       mActionToggleEditing->setChecked( false );
11242       mActionToggleEditing->setEnabled( false );
11243       visibleMessageBar()->pushMessage( tr( "Start editing failed" ),
11244                                         tr( "Layer cannot be edited" ),
11245                                         Qgis::MessageLevel::Warning );
11246     }
11247   }
11248 }
11249 
toggleEditing(QgsMapLayer * layer,bool allowCancel)11250 bool QgisApp::toggleEditing( QgsMapLayer *layer, bool allowCancel )
11251 {
11252   switch ( layer->type() )
11253   {
11254     case QgsMapLayerType::VectorLayer:
11255       return toggleEditingVectorLayer( qobject_cast<QgsVectorLayer *>( layer ), allowCancel );
11256     case QgsMapLayerType::MeshLayer:
11257       return toggleEditingMeshLayer( qobject_cast<QgsMeshLayer *>( layer ), allowCancel );
11258     case QgsMapLayerType::RasterLayer:
11259     case QgsMapLayerType::PluginLayer:
11260     case QgsMapLayerType::VectorTileLayer:
11261     case QgsMapLayerType::AnnotationLayer:
11262     case QgsMapLayerType::PointCloudLayer:
11263       break;
11264   }
11265   return false;
11266 }
11267 
toggleEditingVectorLayer(QgsVectorLayer * vlayer,bool allowCancel)11268 bool QgisApp::toggleEditingVectorLayer( QgsVectorLayer *vlayer, bool allowCancel )
11269 {
11270   if ( !vlayer )
11271   {
11272     return false;
11273   }
11274 
11275   bool res = true;
11276 
11277   QString connString = QgsTransaction::connectionString( vlayer->source() );
11278   QString key = vlayer->providerType();
11279 
11280   QMap< QPair< QString, QString>, QgsTransactionGroup *> transactionGroups = QgsProject::instance()->transactionGroups();
11281   QMap< QPair< QString, QString>, QgsTransactionGroup *>::iterator tIt = transactionGroups .find( qMakePair( key, connString ) );
11282   QgsTransactionGroup *tg = ( tIt != transactionGroups.end() ? tIt.value() : nullptr );
11283 
11284   bool isModified = false;
11285 
11286   // Assume changes if: a) the layer reports modifications or b) its transaction group was modified
11287   QString modifiedLayerNames;
11288   bool hasSeveralModifiedLayers = false;
11289   if ( tg && tg->layers().contains( vlayer ) && tg->modified() )
11290   {
11291     isModified = true;
11292     std::vector<QString> vectModifiedLayerNames;
11293     if ( vlayer->isModified() )
11294     {
11295       vectModifiedLayerNames.push_back( vlayer->name() );
11296     }
11297     for ( QgsVectorLayer *iterLayer : tg->layers() )
11298     {
11299       if ( iterLayer != vlayer && iterLayer->isModified() )
11300       {
11301         vectModifiedLayerNames.push_back( iterLayer->name() );
11302       }
11303     }
11304     if ( vectModifiedLayerNames.size() == 1 )
11305     {
11306       modifiedLayerNames = vectModifiedLayerNames[0];
11307     }
11308     else if ( vectModifiedLayerNames.size() == 2 )
11309     {
11310       modifiedLayerNames = tr( "%1 and %2" ).arg( vectModifiedLayerNames[0] ).arg( vectModifiedLayerNames[1] );
11311     }
11312     else if ( vectModifiedLayerNames.size() > 2 )
11313     {
11314       modifiedLayerNames = tr( "%1, %2, …" ).arg( vectModifiedLayerNames[0] ).arg( vectModifiedLayerNames[1] );
11315     }
11316     hasSeveralModifiedLayers = vectModifiedLayerNames.size() > 1;
11317   }
11318   else if ( vlayer->isModified() )
11319   {
11320     isModified  = true;
11321     modifiedLayerNames = vlayer->name();
11322   }
11323 
11324   if ( !vlayer->isEditable() && !vlayer->readOnly() )
11325   {
11326     if ( !vlayer->supportsEditing() )
11327     {
11328       mActionToggleEditing->setChecked( false );
11329       mActionToggleEditing->setEnabled( false );
11330       visibleMessageBar()->pushMessage( tr( "Start editing failed" ),
11331                                         tr( "Provider cannot be opened for editing" ),
11332                                         Qgis::MessageLevel::Warning );
11333       return false;
11334     }
11335 
11336     vlayer->startEditing();
11337 
11338     QString markerType = QgsSettingsRegistryCore::settingsDigitizingMarkerStyle.value();
11339     bool markSelectedOnly = QgsSettingsRegistryCore::settingsDigitizingMarkerOnlyForSelected.value();
11340 
11341     // redraw only if markers will be drawn
11342     if ( ( !markSelectedOnly || vlayer->selectedFeatureCount() > 0 ) &&
11343          ( markerType == QLatin1String( "Cross" ) || markerType == QLatin1String( "SemiTransparentCircle" ) ) )
11344     {
11345       vlayer->triggerRepaint();
11346     }
11347   }
11348   else if ( isModified )
11349   {
11350     QMessageBox::StandardButtons buttons = QMessageBox::Save | QMessageBox::Discard;
11351     if ( allowCancel )
11352       buttons |= QMessageBox::Cancel;
11353 
11354     switch ( QMessageBox::question( nullptr,
11355                                     tr( "Stop Editing" ),
11356                                     hasSeveralModifiedLayers ?
11357                                     tr( "Do you want to save the changes to layers %1?" ).arg( modifiedLayerNames ) :
11358                                     tr( "Do you want to save the changes to layer %1?" ).arg( modifiedLayerNames ),
11359                                     buttons ) )
11360     {
11361       case QMessageBox::Cancel:
11362         res = false;
11363         break;
11364 
11365       case QMessageBox::Save:
11366         QApplication::setOverrideCursor( Qt::WaitCursor );
11367 
11368         if ( !vlayer->commitChanges() )
11369         {
11370           commitError( vlayer );
11371           // Leave the in-memory editing state alone,
11372           // to give the user a chance to enter different values
11373           // and try the commit again later
11374           res = false;
11375         }
11376 
11377         vlayer->triggerRepaint();
11378 
11379         QApplication::restoreOverrideCursor();
11380         break;
11381 
11382       case QMessageBox::Discard:
11383       {
11384         QApplication::setOverrideCursor( Qt::WaitCursor );
11385 
11386         QgsCanvasRefreshBlocker refreshBlocker;
11387         if ( !vlayer->rollBack() )
11388         {
11389           visibleMessageBar()->pushMessage( tr( "Error" ),
11390                                             tr( "Problems during roll back" ),
11391                                             Qgis::MessageLevel::Critical );
11392           res = false;
11393         }
11394 
11395         vlayer->triggerRepaint();
11396 
11397         QApplication::restoreOverrideCursor();
11398         break;
11399       }
11400 
11401       default:
11402         break;
11403     }
11404   }
11405   else //layer not modified
11406   {
11407     QgsCanvasRefreshBlocker refreshBlocker;
11408     vlayer->rollBack();
11409     res = true;
11410     vlayer->triggerRepaint();
11411   }
11412 
11413   if ( !res && vlayer == activeLayer() )
11414   {
11415     // while also called when layer sends editingStarted/editingStopped signals,
11416     // this ensures correct restoring of gui state if toggling was canceled
11417     // or layer commit/rollback functions failed
11418     activateDeactivateLayerRelatedActions( vlayer );
11419   }
11420 
11421   return res;
11422 }
11423 
toggleEditingMeshLayer(QgsMeshLayer * mlayer,bool allowCancel)11424 bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel )
11425 {
11426   if ( !mlayer )
11427     return false;
11428 
11429   if ( !mlayer->supportsEditing() )
11430     return false;
11431 
11432   bool res = false;
11433 
11434   QgsCoordinateTransform transform( mlayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() );
11435 
11436   if ( !mlayer->isEditable() )
11437   {
11438     QMessageBox *messageBox = new QMessageBox( QMessageBox::NoIcon, tr( "Start Mesh Frame Edit" ),
11439         tr( "Starting editing the frame of this mesh layer will remove all dataset groups.\n"
11440             "Alternatively, you can create a new mesh layer from that one." ), QMessageBox::Cancel );
11441 
11442     messageBox->addButton( tr( "Edit Current Mesh" ), QMessageBox::NoRole );
11443     QPushButton *editCopyButton = messageBox->addButton( tr( "Edit a Copy" ), QMessageBox::NoRole );
11444     messageBox->setDefaultButton( QMessageBox::Cancel );
11445 
11446     messageBox->exec();
11447 
11448     if ( messageBox->clickedButton() == messageBox->button( QMessageBox::Cancel ) )
11449     {
11450       mActionToggleEditing->setChecked( false );
11451       return false;
11452     }
11453     else if ( messageBox->clickedButton() == editCopyButton )
11454     {
11455       QgsNewMeshLayerDialog *newMeshDialog = new QgsNewMeshLayerDialog( this );
11456       newMeshDialog->setSourceMeshLayer( mlayer, true );
11457       if ( newMeshDialog->exec() )
11458         mlayer = newMeshDialog->newLayer();
11459       else
11460       {
11461         mActionToggleEditing->setChecked( false );
11462         return false;
11463       }
11464     }
11465 
11466     res = mlayer->startFrameEditing( transform );
11467     mActionToggleEditing->setChecked( res );
11468 
11469     if ( !res )
11470     {
11471       visibleMessageBar()->pushWarning(
11472         tr( "Mesh editing" ),
11473         tr( "Unable to start mesh editing for layer \"%1\"" ).arg( mlayer->name() ) );
11474     }
11475   }
11476   else if ( mlayer->isModified() )
11477   {
11478     QMessageBox::StandardButtons buttons = QMessageBox::Save | QMessageBox::Discard;
11479     if ( allowCancel )
11480       buttons = buttons | QMessageBox::Cancel;
11481     switch ( QMessageBox::question( nullptr,
11482                                     tr( "Stop Editing" ),
11483                                     tr( "Do you want to save the changes to layer %1?" ).arg( mlayer->name() ),
11484                                     buttons ) )
11485     {
11486       case QMessageBox::Cancel:
11487         res = false;
11488         break;
11489 
11490       case QMessageBox::Save:
11491       {
11492         QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
11493         QgsCanvasRefreshBlocker refreshBlocker;
11494         if ( !mlayer->commitFrameEditing( transform, false ) )
11495         {
11496           visibleMessageBar()->pushWarning(
11497             tr( "Mesh editing" ),
11498             tr( "Unable to save editing for layer \"%1\"" ).arg( mlayer->name() ) );
11499           res = false;
11500         }
11501 
11502         mlayer->triggerRepaint();
11503       }
11504       break;
11505       case QMessageBox::Discard:
11506       {
11507         QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
11508         QgsCanvasRefreshBlocker refreshBlocker;
11509         if ( !mlayer->rollBackFrameEditing( transform, false ) )
11510         {
11511           visibleMessageBar()->pushMessage( tr( "Error" ),
11512                                             tr( "Problems during roll back" ),
11513                                             Qgis::MessageLevel::Critical );
11514           res = false;
11515         }
11516 
11517         mlayer->triggerRepaint();
11518         break;
11519       }
11520 
11521       default:
11522         break;
11523     }
11524   }
11525   else //mesh layer not modified
11526   {
11527     QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
11528     QgsCanvasRefreshBlocker refreshBlocker;
11529     mlayer->rollBackFrameEditing( transform, false );
11530     mlayer->triggerRepaint();
11531   }
11532 
11533   if ( !res && mlayer == activeLayer() )
11534   {
11535     // while also called when layer sends editingStarted/editingStopped signals,
11536     // this ensures correct restoring of gui state if toggling was canceled
11537     // or layer commit/rollback functions failed
11538     activateDeactivateLayerRelatedActions( mlayer );
11539   }
11540 
11541   return res;
11542 }
11543 
saveActiveLayerEdits()11544 void QgisApp::saveActiveLayerEdits()
11545 {
11546   saveEdits( activeLayer(), true, true );
11547 }
11548 
saveEdits(QgsMapLayer * layer,bool leaveEditable,bool triggerRepaint)11549 void QgisApp::saveEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint )
11550 {
11551   if ( !layer )
11552     return;
11553 
11554   switch ( layer->type() )
11555   {
11556     case QgsMapLayerType::VectorLayer:
11557       return saveVectorLayerEdits( layer, leaveEditable, triggerRepaint );
11558     case QgsMapLayerType::MeshLayer:
11559       return saveMeshLayerEdits( layer, leaveEditable, triggerRepaint );
11560     case QgsMapLayerType::RasterLayer:
11561     case QgsMapLayerType::PluginLayer:
11562     case QgsMapLayerType::VectorTileLayer:
11563     case QgsMapLayerType::AnnotationLayer:
11564     case QgsMapLayerType::PointCloudLayer:
11565       break;
11566   }
11567 }
11568 
saveVectorLayerEdits(QgsMapLayer * layer,bool leaveEditable,bool triggerRepaint)11569 void QgisApp::saveVectorLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint )
11570 {
11571   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
11572   if ( !vlayer || !vlayer->isEditable() || !vlayer->isModified() )
11573     return;
11574 
11575   if ( vlayer == activeLayer() )
11576     mSaveRollbackInProgress = true;
11577 
11578   if ( !vlayer->commitChanges( !leaveEditable ) )
11579   {
11580     mSaveRollbackInProgress = false;
11581     commitError( vlayer );
11582   }
11583 
11584   if ( triggerRepaint )
11585   {
11586     vlayer->triggerRepaint();
11587   }
11588 }
11589 
saveMeshLayerEdits(QgsMapLayer * layer,bool leaveEditable,bool triggerRepaint)11590 void QgisApp::saveMeshLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint )
11591 {
11592   QgsMeshLayer *mlayer = qobject_cast<QgsMeshLayer *>( layer );
11593   if ( !mlayer || !mlayer->isEditable() || !mlayer->isModified() )
11594     return;
11595 
11596   if ( mlayer == activeLayer() )
11597     mSaveRollbackInProgress = true;
11598 
11599   QgsCanvasRefreshBlocker refreshBlocker;
11600   QgsCoordinateTransform transform( mlayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() );
11601 
11602   if ( !mlayer->commitFrameEditing( transform, leaveEditable ) )
11603     visibleMessageBar()->pushWarning(
11604       tr( "Mesh editing" ),
11605       tr( "Unable to save editing for layer \"%1\"" ).arg( mlayer->name() ) );
11606 
11607   if ( triggerRepaint )
11608   {
11609     mlayer->triggerRepaint();
11610   }
11611 }
11612 
cancelEdits(QgsMapLayer * layer,bool leaveEditable,bool triggerRepaint)11613 void QgisApp::cancelEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint )
11614 {
11615   if ( !layer )
11616     return;
11617 
11618   switch ( layer->type() )
11619   {
11620     case QgsMapLayerType::VectorLayer:
11621       return cancelVectorLayerEdits( layer, leaveEditable, triggerRepaint );
11622     case QgsMapLayerType::MeshLayer:
11623       return cancelMeshLayerEdits( layer, leaveEditable, triggerRepaint );
11624     case QgsMapLayerType::RasterLayer:
11625     case QgsMapLayerType::PluginLayer:
11626     case QgsMapLayerType::VectorTileLayer:
11627     case QgsMapLayerType::AnnotationLayer:
11628     case QgsMapLayerType::PointCloudLayer:
11629       break;
11630   }
11631 }
11632 
cancelVectorLayerEdits(QgsMapLayer * layer,bool leaveEditable,bool triggerRepaint)11633 void QgisApp::cancelVectorLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint )
11634 {
11635   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
11636   if ( !vlayer || !vlayer->isEditable() )
11637     return;
11638 
11639   if ( vlayer == activeLayer() && leaveEditable )
11640     mSaveRollbackInProgress = true;
11641 
11642   QgsCanvasRefreshBlocker refreshBlocker;
11643   if ( !vlayer->rollBack( !leaveEditable ) )
11644   {
11645     mSaveRollbackInProgress = false;
11646     QMessageBox::warning( nullptr,
11647                           tr( "Error" ),
11648                           tr( "Could not %1 changes to layer %2\n\nErrors: %3\n" )
11649                           .arg( leaveEditable ? tr( "rollback" ) : tr( "cancel" ),
11650                                 vlayer->name(),
11651                                 vlayer->commitErrors().join( QLatin1String( "\n  " ) ) ) );
11652   }
11653 
11654   if ( leaveEditable )
11655   {
11656     vlayer->startEditing();
11657   }
11658   if ( triggerRepaint )
11659   {
11660     vlayer->triggerRepaint();
11661   }
11662 }
11663 
cancelMeshLayerEdits(QgsMapLayer * layer,bool leaveEditable,bool triggerRepaint)11664 void QgisApp::cancelMeshLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint )
11665 {
11666   QgsMeshLayer *mlayer = qobject_cast<QgsMeshLayer *>( layer );
11667   if ( !mlayer || !mlayer->isEditable() )
11668     return;
11669 
11670   if ( mlayer == activeLayer() && leaveEditable )
11671     mSaveRollbackInProgress = true;
11672 
11673   QgsCanvasRefreshBlocker refreshBlocker;
11674   QgsCoordinateTransform transform( mlayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() );
11675   if ( !mlayer->rollBackFrameEditing( transform, leaveEditable ) )
11676   {
11677     mSaveRollbackInProgress = false;
11678     QMessageBox::warning( nullptr,
11679                           tr( "Error" ),
11680                           tr( "Could not %1 changes to layer %2" )
11681                           .arg( leaveEditable ? tr( "rollback" ) : tr( "cancel" ),
11682                                 mlayer->name() ) );
11683   }
11684 
11685   if ( triggerRepaint )
11686   {
11687     mlayer->triggerRepaint();
11688   }
11689 }
11690 
enableMeshEditingTools(bool enable)11691 void QgisApp::enableMeshEditingTools( bool enable )
11692 {
11693   if ( !mMapTools )
11694     return;
11695   QgsMapToolEditMeshFrame *editMeshMapTool = qobject_cast<QgsMapToolEditMeshFrame *>( mMapTools->mapTool( QgsAppMapTools::EditMeshFrame ) );
11696 
11697   editMeshMapTool->setActionsEnable( enable );
11698 }
11699 
captureTools()11700 QList<QgsMapToolCapture *> QgisApp::captureTools()
11701 {
11702   QList< QgsMapToolCapture * > res = mMapTools->captureTools();
11703   // also check current tool, in case it's a custom tool
11704   if ( QgsMapToolCapture *currentTool = qobject_cast< QgsMapToolCapture * >( mMapCanvas->mapTool() ) )
11705   {
11706     if ( !res.contains( currentTool ) )
11707       res.append( currentTool );
11708   }
11709   return res;
11710 }
11711 
saveEdits()11712 void QgisApp::saveEdits()
11713 {
11714   const auto constSelectedLayers = mLayerTreeView->selectedLayers();
11715   for ( QgsMapLayer *layer : constSelectedLayers )
11716   {
11717     saveEdits( layer, true, false );
11718   }
11719   refreshMapCanvas();
11720   activateDeactivateLayerRelatedActions( activeLayer() );
11721 }
11722 
saveAllEdits(bool verifyAction)11723 void QgisApp::saveAllEdits( bool verifyAction )
11724 {
11725   if ( verifyAction )
11726   {
11727     if ( !verifyEditsActionDialog( tr( "Save" ), tr( "all" ) ) )
11728       return;
11729   }
11730 
11731   const auto layers = editableLayers( true, true );
11732   for ( QgsMapLayer *layer : layers )
11733   {
11734     saveEdits( layer, true, false );
11735   }
11736   refreshMapCanvas();
11737   activateDeactivateLayerRelatedActions( activeLayer() );
11738 }
11739 
rollbackEdits()11740 void QgisApp::rollbackEdits()
11741 {
11742   const auto constSelectedLayers = mLayerTreeView->selectedLayers();
11743   for ( QgsMapLayer *layer : constSelectedLayers )
11744   {
11745     cancelEdits( layer, true, false );
11746   }
11747   refreshMapCanvas();
11748   activateDeactivateLayerRelatedActions( activeLayer() );
11749 }
11750 
rollbackAllEdits(bool verifyAction)11751 void QgisApp::rollbackAllEdits( bool verifyAction )
11752 {
11753   if ( verifyAction )
11754   {
11755     if ( !verifyEditsActionDialog( tr( "Rollback" ), tr( "all" ) ) )
11756       return;
11757   }
11758 
11759   const auto layers = editableLayers( true, true );
11760   for ( QgsMapLayer *layer : layers )
11761   {
11762     cancelEdits( layer, true, false );
11763   }
11764   refreshMapCanvas();
11765   activateDeactivateLayerRelatedActions( activeLayer() );
11766 }
11767 
cancelEdits()11768 void QgisApp::cancelEdits()
11769 {
11770   const auto constSelectedLayers = mLayerTreeView->selectedLayers();
11771   for ( QgsMapLayer *layer : constSelectedLayers )
11772   {
11773     cancelEdits( layer, false, false );
11774   }
11775   refreshMapCanvas();
11776   activateDeactivateLayerRelatedActions( activeLayer() );
11777 }
11778 
cancelAllEdits(bool verifyAction)11779 void QgisApp::cancelAllEdits( bool verifyAction )
11780 {
11781   if ( verifyAction )
11782   {
11783     if ( !verifyEditsActionDialog( tr( "Cancel" ), tr( "all" ) ) )
11784       return;
11785   }
11786 
11787   const auto layers = editableLayers( false, true );
11788   for ( QgsMapLayer *layer : layers )
11789   {
11790     cancelEdits( layer, false, false );
11791   }
11792   refreshMapCanvas();
11793   activateDeactivateLayerRelatedActions( activeLayer() );
11794 }
11795 
verifyEditsActionDialog(const QString & act,const QString & upon)11796 bool QgisApp::verifyEditsActionDialog( const QString &act, const QString &upon )
11797 {
11798   bool res = false;
11799   switch ( QMessageBox::question( nullptr,
11800                                   tr( "Current edits" ),
11801                                   tr( "%1 current changes for %2 layer(s)?" )
11802                                   .arg( act,
11803                                         upon ),
11804                                   QMessageBox::Yes | QMessageBox::No ) )
11805   {
11806     case QMessageBox::Yes:
11807       res = true;
11808       break;
11809     default:
11810       break;
11811   }
11812   return res;
11813 }
11814 
updateLayerModifiedActions()11815 void QgisApp::updateLayerModifiedActions()
11816 {
11817   bool enableSaveLayerEdits = false;
11818 
11819   QgsMapLayer *currentLayer = activeLayer();
11820   if ( currentLayer )
11821   {
11822     switch ( currentLayer->type() )
11823     {
11824       case QgsMapLayerType::VectorLayer:
11825       {
11826         QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( currentLayer );
11827         if ( QgsVectorDataProvider *dprovider = vlayer->dataProvider() )
11828         {
11829           enableSaveLayerEdits = ( dprovider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues
11830                                    && vlayer->isEditable()
11831                                    && vlayer->isModified() );
11832         }
11833       }
11834       break;
11835       case QgsMapLayerType::MeshLayer:
11836       {
11837         QgsMeshLayer *mlayer = qobject_cast<QgsMeshLayer *>( currentLayer );
11838         enableSaveLayerEdits = ( mlayer->isEditable() && mlayer->isModified() );
11839       }
11840       break;
11841       case QgsMapLayerType::RasterLayer:
11842       case QgsMapLayerType::PluginLayer:
11843       case QgsMapLayerType::VectorTileLayer:
11844       case QgsMapLayerType::AnnotationLayer:
11845       case QgsMapLayerType::PointCloudLayer:
11846         break;
11847     }
11848   }
11849 
11850   mActionSaveLayerEdits->setEnabled( enableSaveLayerEdits );
11851 
11852   QList<QgsLayerTreeLayer *> selectedLayerNodes = mLayerTreeView ? mLayerTreeView->selectedLayerNodes() : QList<QgsLayerTreeLayer *>();
11853 
11854   mActionSaveEdits->setEnabled( QgsLayerTreeUtils::layersModified( selectedLayerNodes ) );
11855   mActionRollbackEdits->setEnabled( QgsLayerTreeUtils::layersModified( selectedLayerNodes ) );
11856   mActionCancelEdits->setEnabled( QgsLayerTreeUtils::layersEditable( selectedLayerNodes ) );
11857 
11858   bool hasEditLayers = !editableLayers( false, true ).isEmpty();
11859   mActionAllEdits->setEnabled( hasEditLayers );
11860   mActionCancelAllEdits->setEnabled( hasEditLayers );
11861 
11862   bool hasModifiedLayers = !editableLayers( true, true ).isEmpty();
11863   mActionSaveAllEdits->setEnabled( hasModifiedLayers );
11864   mActionRollbackAllEdits->setEnabled( hasModifiedLayers );
11865 }
11866 
editableLayers(bool modified,bool ignoreLayersWhichCannotBeToggled) const11867 QList<QgsMapLayer *> QgisApp::editableLayers( bool modified, bool ignoreLayersWhichCannotBeToggled ) const
11868 {
11869   QList<QgsMapLayer *> editLayers;
11870   // use legend layers (instead of registry) so QList mirrors its order
11871   const auto constFindLayers = mLayerTreeView->layerTreeModel()->rootGroup()->findLayers();
11872   for ( QgsLayerTreeLayer *nodeLayer : constFindLayers )
11873   {
11874     QgsMapLayer *layer = nodeLayer->layer();
11875     if ( !layer )
11876       continue;
11877 
11878     if ( layer->isEditable() && ( !modified || layer->isModified() ) && ( !ignoreLayersWhichCannotBeToggled || !( layer->properties() & Qgis::MapLayerProperty::UsersCannotToggleEditing ) ) )
11879       editLayers << layer;
11880   }
11881   return editLayers;
11882 }
11883 
duplicateVectorStyle(QgsVectorLayer * srcLayer,QgsVectorLayer * destLayer)11884 void QgisApp::duplicateVectorStyle( QgsVectorLayer *srcLayer, QgsVectorLayer *destLayer )
11885 {
11886   // copy symbology, if possible
11887   if ( srcLayer->geometryType() == destLayer->geometryType() )
11888   {
11889     QDomImplementation DomImplementation;
11890     QDomDocumentType documentType =
11891       DomImplementation.createDocumentType(
11892         QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
11893     QDomDocument doc( documentType );
11894     QDomElement rootNode = doc.createElement( QStringLiteral( "qgis" ) );
11895     rootNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
11896     doc.appendChild( rootNode );
11897     QString errorMsg;
11898     QgsReadWriteContext writeContext = QgsReadWriteContext();
11899     srcLayer->writeSymbology( rootNode, doc, errorMsg, writeContext );
11900     QgsReadWriteContext readContext = QgsReadWriteContext();
11901     destLayer->readSymbology( rootNode, errorMsg, readContext );
11902   }
11903 }
11904 
11905 
layerSubsetString()11906 void QgisApp::layerSubsetString()
11907 {
11908   layerSubsetString( activeLayer() );
11909 }
11910 
layerSubsetString(QgsMapLayer * mapLayer)11911 void QgisApp::layerSubsetString( QgsMapLayer *mapLayer )
11912 {
11913 
11914   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer );
11915   if ( !vlayer )
11916   {
11917     // Try PG raster
11918     QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( mapLayer );
11919     if ( rlayer )
11920     {
11921       QgsRasterDataProvider *provider = rlayer->dataProvider();
11922       if ( provider &&
11923            provider->supportsSubsetString() )
11924       {
11925         // PG raster is the only one for now
11926         if ( provider->name() == QLatin1String( "postgresraster" ) )
11927         {
11928           // We need a vector for the sql editor
11929           QgsDataSourceUri vectorUri { provider->dataSourceUri() };
11930           vectorUri.setGeometryColumn( QString() );
11931           vectorUri.setSrid( QString() );
11932           QgsVectorLayer vlayer { vectorUri.uri( ), QStringLiteral( "pgrasterlayer" ), QStringLiteral( "postgres" ) };
11933           if ( vlayer.isValid( ) )
11934           {
11935             // launch the query builder
11936             QgsQueryBuilder qb { &vlayer };
11937             QString subsetBefore = vlayer.subsetString();
11938 
11939             // Set the sql in the query builder to the same in the prop dialog
11940             // (in case the user has already changed it)
11941             qb.setSql( rlayer->subsetString() );
11942             // Open the query builder and refresh symbology if sql has changed
11943             // Note: repaintRequested is emitted directly from QgsQueryBuilder
11944             //       when the sql is set in the layer.
11945             if ( qb.exec() && ( subsetBefore != qb.sql() ) && mLayerTreeView )
11946             {
11947               if ( rlayer->setSubsetString( qb.sql() ) )
11948               {
11949                 mLayerTreeView->refreshLayerSymbology( rlayer->id() );
11950                 activateDeactivateLayerRelatedActions( rlayer );
11951               }
11952             }
11953           }
11954         }
11955       }
11956     }
11957     return;
11958   }
11959 
11960 
11961   bool joins = !vlayer->vectorJoins().isEmpty();
11962   if ( vlayer->vectorJoins().size() == 1 )
11963   {
11964     QgsVectorLayerJoinInfo info = vlayer->vectorJoins()[0];
11965     joins = !vlayer->joinBuffer()->isAuxiliaryJoin( info );
11966   }
11967 
11968   if ( joins )
11969   {
11970     if ( QMessageBox::question( nullptr, tr( "Filter on Joined Fields" ),
11971                                 tr( "You are about to set a subset filter on a layer that has joined fields. "
11972                                     "Joined fields cannot be filtered, unless you convert the layer to a virtual layer first. "
11973                                     "Would you like to create a virtual layer out of this layer first?" ),
11974                                 QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes )
11975     {
11976       QgsVirtualLayerDefinition def = QgsVirtualLayerDefinitionUtils::fromJoinedLayer( vlayer );
11977       const QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
11978       QgsVectorLayer *newLayer = new QgsVectorLayer( def.toString(), vlayer->name() + " (virtual)", QStringLiteral( "virtual" ), options );
11979       if ( newLayer->isValid() )
11980       {
11981         duplicateVectorStyle( vlayer, newLayer );
11982         QgsProject::instance()->addMapLayer( newLayer, /*addToLegend*/ false, /*takeOwnership*/ true );
11983         QgsLayerTreeUtils::insertLayerBelow( QgsProject::instance()->layerTreeRoot(), vlayer, newLayer );
11984         mLayerTreeView->setCurrentLayer( newLayer );
11985         // hide the old layer
11986         QgsLayerTreeLayer *vLayerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( vlayer->id() );
11987         if ( vLayerTreeLayer )
11988           vLayerTreeLayer->setItemVisibilityChecked( false );
11989         vlayer = newLayer;
11990       }
11991       else
11992       {
11993         delete newLayer;
11994       }
11995     }
11996   }
11997 
11998   // launch the query builder
11999   std::unique_ptr<QgsSubsetStringEditorInterface> qb( QgsGui::subsetStringEditorProviderRegistry()->createDialog( vlayer, this ) );
12000   QString subsetBefore = vlayer->subsetString();
12001 
12002   // Set the sql in the query builder to the same in the prop dialog
12003   // (in case the user has already changed it)
12004   qb->setSubsetString( vlayer->subsetString() );
12005   // Open the query builder and refresh symbology if sql has changed
12006   // Note: repaintRequested is emitted directly from QgsQueryBuilder
12007   //       when the sql is set in the layer.
12008   if ( qb->exec() && ( subsetBefore != qb->subsetString() ) && mLayerTreeView )
12009   {
12010     mLayerTreeView->refreshLayerSymbology( vlayer->id() );
12011     activateDeactivateLayerRelatedActions( vlayer );
12012   }
12013 }
12014 
saveLastMousePosition(const QgsPointXY & p)12015 void QgisApp::saveLastMousePosition( const QgsPointXY &p )
12016 {
12017   if ( mMapTipsVisible )
12018   {
12019     // store the point, we need it for when the maptips timer fires
12020     mLastMapPosition = p;
12021 
12022     // we use this slot to control the timer for maptips since it is fired each time
12023     // the mouse moves.
12024     if ( mMapCanvas->underMouse() )
12025     {
12026       // Clear the maptip (this is done conditionally)
12027       mpMaptip->clear( mMapCanvas );
12028       // don't start the timer if the mouse is not over the map canvas
12029       mpMapTipsTimer->start();
12030     }
12031   }
12032 }
12033 
12034 
showScale(double scale)12035 void QgisApp::showScale( double scale )
12036 {
12037   mScaleWidget->setScale( scale );
12038 }
12039 
12040 
userRotation()12041 void QgisApp::userRotation()
12042 {
12043   double degrees = mRotationEdit->value();
12044   mMapCanvas->setRotation( degrees );
12045   mMapCanvas->refresh();
12046 }
12047 
projectCrsChanged()12048 void QgisApp::projectCrsChanged()
12049 {
12050   updateCrsStatusBar();
12051   QgsDebugMsgLevel( QStringLiteral( "QgisApp::setupConnections -1- : QgsProject::instance()->crs().description[%1]ellipsoid[%2]" ).arg( QgsProject::instance()->crs().description(), QgsProject::instance()->crs().ellipsoidAcronym() ), 3 );
12052   mMapCanvas->setDestinationCrs( QgsProject::instance()->crs() );
12053 
12054   // handle datum transforms
12055   QList<QgsCoordinateReferenceSystem> alreadyAsked;
12056   QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
12057   for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
12058   {
12059     if ( !alreadyAsked.contains( it.value()->crs() ) )
12060     {
12061       alreadyAsked.append( it.value()->crs() );
12062       askUserForDatumTransform( it.value()->crs(),
12063                                 QgsProject::instance()->crs(), it.value() );
12064     }
12065   }
12066 }
12067 
projectTemporalRangeChanged()12068 void QgisApp::projectTemporalRangeChanged()
12069 {
12070   QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
12071   QgsMapLayer *currentLayer = nullptr;
12072 
12073   for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
12074   {
12075     currentLayer = it.value();
12076 
12077     if ( currentLayer->dataProvider() )
12078     {
12079       if ( QgsProviderMetadata *metadata = QgsProviderRegistry::instance()->providerMetadata(
12080                                              currentLayer->providerType() ) )
12081       {
12082         QVariantMap uri = metadata->decodeUri( currentLayer->dataProvider()->dataSourceUri() );
12083 
12084         if ( uri.contains( QStringLiteral( "temporalSource" ) ) &&
12085              uri.value( QStringLiteral( "temporalSource" ) ).toString() == QLatin1String( "project" ) )
12086         {
12087           QgsDateTimeRange range = QgsProject::instance()->timeSettings()->temporalRange();
12088           if ( range.begin().isValid() && range.end().isValid() )
12089           {
12090             QString time = range.begin().toString( Qt::ISODateWithMs ) + '/' +
12091                            range.end().toString( Qt::ISODateWithMs );
12092 
12093             uri[ QStringLiteral( "time" ) ] = time;
12094 
12095             currentLayer->setDataSource( metadata->encodeUri( uri ), currentLayer->name(), currentLayer->providerType(), QgsDataProvider::ProviderOptions() );
12096           }
12097         }
12098       }
12099     }
12100   }
12101 }
12102 
12103 // toggle overview status
isInOverview()12104 void QgisApp::isInOverview()
12105 {
12106   mLayerTreeView->defaultActions()->showInOverview();
12107 }
12108 
removingLayers(const QStringList & layers)12109 void QgisApp::removingLayers( const QStringList &layers )
12110 {
12111   const auto constLayers = layers;
12112   for ( const QString &layerId : constLayers )
12113   {
12114     QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>(
12115                                QgsProject::instance()->mapLayer( layerId ) );
12116     if ( !vlayer || !vlayer->isEditable() )
12117       return;
12118 
12119     toggleEditing( vlayer, false );
12120   }
12121 }
12122 
removeLayer()12123 void QgisApp::removeLayer()
12124 {
12125   if ( !mLayerTreeView )
12126   {
12127     return;
12128   }
12129 
12130   // look for layers recursively so we catch also those that are within selected groups
12131   const QList<QgsMapLayer *> selectedLayers = mLayerTreeView->selectedLayersRecursive();
12132 
12133   QStringList nonRemovableLayerNames;
12134   for ( QgsMapLayer *layer : selectedLayers )
12135   {
12136     if ( !layer->flags().testFlag( QgsMapLayer::Removable ) )
12137       nonRemovableLayerNames << layer->name();
12138   }
12139   if ( !nonRemovableLayerNames.isEmpty() )
12140   {
12141     QMessageBox::warning( this, tr( "Required Layers" ),
12142                           tr( "The following layers are marked as required by the project:\n\n%1\n\nPlease deselect them (or unmark as required) and retry." ).arg( nonRemovableLayerNames.join( QLatin1Char( '\n' ) ) ) );
12143     return;
12144   }
12145 
12146   for ( QgsMapLayer *layer : selectedLayers )
12147   {
12148     QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
12149     if ( vlayer && vlayer->isEditable() && !toggleEditing( vlayer, true ) )
12150       return;
12151   }
12152 
12153   QStringList activeTaskDescriptions;
12154   for ( QgsMapLayer *layer : selectedLayers )
12155   {
12156     QList< QgsTask * > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layer );
12157     if ( !tasks.isEmpty() )
12158     {
12159       const auto constTasks = tasks;
12160       for ( QgsTask *task : constTasks )
12161       {
12162         activeTaskDescriptions << tr( " • %1" ).arg( task->description() );
12163       }
12164     }
12165   }
12166 
12167   if ( !activeTaskDescriptions.isEmpty() )
12168   {
12169     QMessageBox::warning( this, tr( "Active Tasks" ),
12170                           tr( "The following tasks are currently running which depend on this layer:\n\n%1\n\nPlease cancel these tasks and retry." ).arg( activeTaskDescriptions.join( QLatin1Char( '\n' ) ) ) );
12171     return;
12172   }
12173 
12174   QList<QgsLayerTreeNode *> selectedNodes = mLayerTreeView->selectedNodes( true );
12175 
12176   //validate selection
12177   if ( selectedNodes.isEmpty() )
12178   {
12179     visibleMessageBar()->pushMessage( tr( "No legend entries selected" ),
12180                                       tr( "Select the layers and groups you want to remove in the legend." ),
12181                                       Qgis::MessageLevel::Info );
12182     return;
12183   }
12184 
12185   bool promptConfirmation = QgsSettings().value( QStringLiteral( "qgis/askToDeleteLayers" ), true ).toBool();
12186 
12187   // Don't show prompt to remove a empty group.
12188   if ( selectedNodes.count() == 1
12189        && selectedNodes.at( 0 )->nodeType() == QgsLayerTreeNode::NodeGroup
12190        && selectedNodes.at( 0 )->children().count() == 0 )
12191   {
12192     promptConfirmation = false;
12193   }
12194 
12195   bool shiftHeld = QApplication::queryKeyboardModifiers().testFlag( Qt::ShiftModifier );
12196 
12197   // Check if there are any hidden layer elements and display a confirmation dialog
12198   QStringList hiddenLayerNames;
12199   auto harvest = [ &hiddenLayerNames ]( const QgsLayerTreeNode * parent )
12200   {
12201     const auto cChildren { parent->children() };
12202     for ( const auto &c : cChildren )
12203     {
12204       if ( QgsLayerTree::isLayer( c ) )
12205       {
12206         const auto treeLayer { QgsLayerTree::toLayer( c ) };
12207         if ( treeLayer->layer() && treeLayer->layer()->flags().testFlag( QgsMapLayer::LayerFlag::Private ) )
12208         {
12209           hiddenLayerNames.push_back( treeLayer->layer()->name( ) );
12210         }
12211       }
12212     }
12213   };
12214 
12215   for ( const auto &n : std::as_const( selectedNodes ) )
12216   {
12217     harvest( n );
12218   }
12219 
12220   QString message { tr( "Remove %n legend entries?", "number of legend items to remove", selectedNodes.count() ) };
12221   if ( ! hiddenLayerNames.isEmpty() )
12222   {
12223     if ( hiddenLayerNames.count( ) > 10 )
12224     {
12225       const int layerCount { hiddenLayerNames.count( ) };
12226       hiddenLayerNames = hiddenLayerNames.mid( 0, 10 );
12227       hiddenLayerNames.push_back( tr( "(%n more hidden layers)",  "number of hidden layers not shown", layerCount - 10 ) );
12228     }
12229     message.append( tr( "The following hidden layers will be removed:\n%1" ).arg( hiddenLayerNames.join( '\n' ) ) );
12230   }
12231 
12232   if ( !shiftHeld && promptConfirmation && QMessageBox::warning( this, tr( "Remove layers and groups" ), message, QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel )
12233   {
12234     return;
12235   }
12236 
12237   const auto constSelectedNodes = selectedNodes;
12238   for ( QgsLayerTreeNode *node : constSelectedNodes )
12239   {
12240     QgsLayerTreeGroup *parentGroup = qobject_cast<QgsLayerTreeGroup *>( node->parent() );
12241     if ( parentGroup )
12242       parentGroup->removeChildNode( node );
12243   }
12244 
12245   showStatusMessage( tr( "%n legend entries removed.", "number of removed legend entries", selectedNodes.count() ) );
12246 
12247   refreshMapCanvas();
12248 }
12249 
duplicateLayers(const QList<QgsMapLayer * > & lyrList)12250 void QgisApp::duplicateLayers( const QList<QgsMapLayer *> &lyrList )
12251 {
12252   if ( !mLayerTreeView )
12253   {
12254     return;
12255   }
12256 
12257   const QList<QgsMapLayer *> selectedLyrs = lyrList.empty() ? mLayerTreeView->selectedLayers() : lyrList;
12258   if ( selectedLyrs.empty() )
12259   {
12260     return;
12261   }
12262 
12263   QgsCanvasRefreshBlocker refreshBlocker;
12264   QgsMapLayer *dupLayer = nullptr;
12265   QgsMapLayer *newSelection = nullptr;
12266   QString layerDupName, unSppType;
12267   QList<QgsMessageBarItem *> msgBars;
12268 
12269   msgBars.reserve( selectedLyrs.size() );
12270   for ( QgsMapLayer *selectedLyr : selectedLyrs )
12271   {
12272     dupLayer = nullptr;
12273     unSppType.clear();
12274     layerDupName = selectedLyr->name() + ' ' + tr( "copy" );
12275 
12276     switch ( selectedLyr->type() )
12277     {
12278       case QgsMapLayerType::PluginLayer:
12279         unSppType = tr( "Plugin layer" );
12280         break;
12281 
12282       case QgsMapLayerType::VectorLayer:
12283       {
12284         if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( selectedLyr ) )
12285         {
12286           if ( vlayer->auxiliaryLayer() )
12287             vlayer->auxiliaryLayer()->save();
12288 
12289           dupLayer = vlayer->clone();
12290         }
12291         break;
12292       }
12293 
12294       case QgsMapLayerType::PointCloudLayer:
12295       case QgsMapLayerType::RasterLayer:
12296       case QgsMapLayerType::VectorTileLayer:
12297       case QgsMapLayerType::MeshLayer:
12298       case QgsMapLayerType::AnnotationLayer:
12299       {
12300         dupLayer = selectedLyr->clone();
12301         break;
12302       }
12303 
12304     }
12305 
12306     if ( dupLayer && !dupLayer->isValid() )
12307     {
12308       msgBars.append( new QgsMessageBarItem(
12309                         tr( "Duplicate layer: " ),
12310                         tr( "%1 (duplication resulted in invalid layer)" ).arg( selectedLyr->name() ),
12311                         Qgis::MessageLevel::Warning,
12312                         0,
12313                         mInfoBar ) );
12314       continue;
12315     }
12316     else if ( !unSppType.isEmpty() || !dupLayer )
12317     {
12318       msgBars.append( new QgsMessageBarItem(
12319                         tr( "Duplicate layer: " ),
12320                         tr( "%1 (%2 type unsupported)" )
12321                         .arg( selectedLyr->name(),
12322                               !unSppType.isEmpty() ? QStringLiteral( "'" ) + unSppType + "' " : QString() ),
12323                         Qgis::MessageLevel::Warning,
12324                         0,
12325                         mInfoBar ) );
12326       continue;
12327     }
12328 
12329     dupLayer->setName( layerDupName );
12330 
12331     // add layer to layer registry and legend
12332     QList<QgsMapLayer *> myList;
12333     myList << dupLayer;
12334     QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( false );
12335     QgsProject::instance()->addMapLayers( myList );
12336     QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( true );
12337 
12338     QgsLayerTreeLayer *nodeSelectedLyr = mLayerTreeView->layerTreeModel()->rootGroup()->findLayer( selectedLyr->id() );
12339     Q_ASSERT( nodeSelectedLyr );
12340     Q_ASSERT( QgsLayerTree::isGroup( nodeSelectedLyr->parent() ) );
12341     QgsLayerTreeGroup *parentGroup = QgsLayerTree::toGroup( nodeSelectedLyr->parent() );
12342 
12343     QgsLayerTreeLayer *nodeDupLayer = parentGroup->insertLayer( parentGroup->children().indexOf( nodeSelectedLyr ) + 1, dupLayer );
12344 
12345     // always set duplicated layers to not visible so layer can be configured before being turned on
12346     nodeDupLayer->setItemVisibilityChecked( false );
12347 
12348     // duplicate the layer style
12349     QString errMsg;
12350     QDomDocument style;
12351     QgsReadWriteContext context;
12352     selectedLyr->exportNamedStyle( style, errMsg, context );
12353     if ( errMsg.isEmpty() )
12354       dupLayer->importNamedStyle( style, errMsg );
12355     if ( !errMsg.isEmpty() )
12356       visibleMessageBar()->pushMessage( errMsg,
12357                                         tr( "Cannot copy style to duplicated layer." ),
12358                                         Qgis::MessageLevel::Critical );
12359     else if ( qobject_cast<QgsVectorLayer *>( dupLayer ) )
12360       visibleMessageBar()->pushMessage( tr( "Layer duplication complete" ),
12361                                         dupLayer->providerType() != QLatin1String( "memory" ) ? tr( "Note that it's using the same data source." ) : QString(),
12362                                         Qgis::MessageLevel::Info );
12363 
12364     if ( !newSelection )
12365       newSelection = dupLayer;
12366   }
12367 
12368   dupLayer = nullptr;
12369 
12370   // auto select first new duplicate layer
12371   if ( newSelection )
12372     setActiveLayer( newSelection );
12373 
12374   // display errors in message bar after duplication of layers
12375   for ( QgsMessageBarItem *msgBar : std::as_const( msgBars ) )
12376   {
12377     mInfoBar->pushItem( msgBar );
12378   }
12379 }
12380 
setLayerScaleVisibility()12381 void QgisApp::setLayerScaleVisibility()
12382 {
12383   if ( !mLayerTreeView )
12384     return;
12385 
12386   QList<QgsMapLayer *> layers = mLayerTreeView->selectedLayers();
12387 
12388   if ( layers.length() < 1 )
12389     return;
12390 
12391   QgsScaleVisibilityDialog *dlg = new QgsScaleVisibilityDialog( this, tr( "Set scale visibility for selected layers" ), mMapCanvas );
12392   QgsMapLayer *layer = mLayerTreeView->currentLayer();
12393   if ( layer )
12394   {
12395     dlg->setScaleVisiblity( layer->hasScaleBasedVisibility() );
12396     dlg->setMinimumScale( layer->minimumScale() );
12397     dlg->setMaximumScale( layer->maximumScale() );
12398   }
12399   if ( dlg->exec() )
12400   {
12401     QgsCanvasRefreshBlocker refreshBlocker;
12402     const auto constLayers = layers;
12403     for ( QgsMapLayer *layer : constLayers )
12404     {
12405       layer->setScaleBasedVisibility( dlg->hasScaleVisibility() );
12406       layer->setMaximumScale( dlg->maximumScale() );
12407       layer->setMinimumScale( dlg->minimumScale() );
12408     }
12409   }
12410   delete dlg;
12411 }
12412 
zoomToLayerScale()12413 void QgisApp::zoomToLayerScale()
12414 {
12415   if ( !mLayerTreeView )
12416     return;
12417 
12418   QList<QgsMapLayer *> layers = mLayerTreeView->selectedLayers();
12419 
12420   if ( layers.length() < 1 )
12421     return;
12422 
12423   QgsMapLayer *layer = mLayerTreeView->currentLayer();
12424   if ( layer && layer->hasScaleBasedVisibility() )
12425   {
12426     const double scale = mMapCanvas->scale();
12427     if ( scale > layer->minimumScale() && layer->minimumScale() > 0 )
12428     {
12429       mMapCanvas->zoomScale( layer->minimumScale() * Qgis::SCALE_PRECISION );
12430     }
12431     else if ( scale <= layer->maximumScale() && layer->maximumScale() > 0 )
12432     {
12433       mMapCanvas->zoomScale( layer->maximumScale() );
12434     }
12435   }
12436 }
12437 
setLayerCrs()12438 void QgisApp::setLayerCrs()
12439 {
12440   if ( !( mLayerTreeView && mLayerTreeView->currentLayer() ) )
12441   {
12442     return;
12443   }
12444 
12445   QgsProjectionSelectionDialog mySelector( this );
12446   mySelector.setCrs( mLayerTreeView->currentLayer()->crs() );
12447 
12448   if ( !mLayerTreeView->currentLayer()->crs().isValid() )
12449     mySelector.showNoCrsForLayerMessage();
12450 
12451   if ( !mySelector.exec() )
12452   {
12453     QApplication::restoreOverrideCursor();
12454     return;
12455   }
12456 
12457   QgsCoordinateReferenceSystem crs = mySelector.crs();
12458 
12459   const auto constSelectedNodes = mLayerTreeView->selectedNodes();
12460   for ( QgsLayerTreeNode *node : constSelectedNodes )
12461   {
12462     if ( QgsLayerTree::isGroup( node ) )
12463     {
12464       const auto constFindLayers = QgsLayerTree::toGroup( node )->findLayers();
12465       for ( QgsLayerTreeLayer *child : constFindLayers )
12466       {
12467         if ( child->layer() )
12468         {
12469           askUserForDatumTransform( crs, QgsProject::instance()->crs(), child->layer() );
12470           child->layer()->setCrs( crs );
12471           child->layer()->triggerRepaint();
12472         }
12473       }
12474     }
12475     else if ( QgsLayerTree::isLayer( node ) )
12476     {
12477       QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
12478       if ( nodeLayer->layer() )
12479       {
12480         askUserForDatumTransform( crs, QgsProject::instance()->crs(), nodeLayer->layer() );
12481         nodeLayer->layer()->setCrs( crs );
12482         nodeLayer->layer()->triggerRepaint();
12483       }
12484     }
12485   }
12486 
12487   refreshMapCanvas();
12488 }
12489 
setProjectCrsFromLayer()12490 void QgisApp::setProjectCrsFromLayer()
12491 {
12492   if ( !( mLayerTreeView && mLayerTreeView->currentLayer() ) )
12493   {
12494     return;
12495   }
12496 
12497   QgsCoordinateReferenceSystem crs = mLayerTreeView->currentLayer()->crs();
12498   QgsCanvasRefreshBlocker refreshBlocker;
12499   QgsProject::instance()->setCrs( crs );
12500 }
12501 
12502 
legendLayerZoomNative()12503 void QgisApp::legendLayerZoomNative()
12504 {
12505   if ( !mLayerTreeView )
12506     return;
12507 
12508   //find current Layer
12509   QgsMapLayer *currentLayer = mLayerTreeView->currentLayer();
12510   if ( !currentLayer )
12511     return;
12512 
12513   if ( QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( currentLayer ) )
12514   {
12515     QgsDebugMsgLevel( "Raster units per pixel  : " + QString::number( layer->rasterUnitsPerPixelX() ), 2 );
12516     QgsDebugMsgLevel( "MapUnitsPerPixel before : " + QString::number( mMapCanvas->mapUnitsPerPixel() ), 2 );
12517 
12518     QList< double >nativeResolutions;
12519     if ( layer->dataProvider() )
12520     {
12521       nativeResolutions = layer->dataProvider()->nativeResolutions();
12522     }
12523 
12524     // get length of central canvas pixel width in source raster crs
12525     QgsRectangle e = mMapCanvas->extent();
12526     QSize s = mMapCanvas->mapSettings().outputSize();
12527     QgsPointXY p1( e.center().x(), e.center().y() );
12528     QgsPointXY p2( e.center().x() + e.width() / s.width(), e.center().y() + e.height() / s.height() );
12529     QgsCoordinateTransform ct( mMapCanvas->mapSettings().destinationCrs(), layer->crs(), QgsProject::instance() );
12530     p1 = ct.transform( p1 );
12531     p2 = ct.transform( p2 );
12532     const double diagonalSize = std::sqrt( p1.sqrDist( p2 ) ); // width (actually the diagonal) of reprojected pixel
12533     if ( !nativeResolutions.empty() )
12534     {
12535       // find closest native resolution
12536       QList< double > diagonalNativeResolutions;
12537       diagonalNativeResolutions.reserve( nativeResolutions.size() );
12538       for ( double d : nativeResolutions )
12539         diagonalNativeResolutions << std::sqrt( 2 * d * d );
12540 
12541       int i;
12542       for ( i = 0; i < diagonalNativeResolutions.size() && diagonalNativeResolutions.at( i ) < diagonalSize; i++ )
12543       {
12544         QgsDebugMsgLevel( QStringLiteral( "test resolution %1: %2" ).arg( i ).arg( diagonalNativeResolutions.at( i ) ), 2 );
12545       }
12546       if ( i == nativeResolutions.size() ||
12547            ( i > 0 && ( ( diagonalNativeResolutions.at( i ) - diagonalSize ) > ( diagonalSize - diagonalNativeResolutions.at( i - 1 ) ) ) ) )
12548       {
12549         QgsDebugMsgLevel( QStringLiteral( "previous resolution" ), 2 );
12550         i--;
12551       }
12552 
12553       mMapCanvas->zoomByFactor( nativeResolutions.at( i ) / mMapCanvas->mapUnitsPerPixel() );
12554     }
12555     else
12556     {
12557       mMapCanvas->zoomByFactor( std::sqrt( layer->rasterUnitsPerPixelX() * layer->rasterUnitsPerPixelX() + layer->rasterUnitsPerPixelY() * layer->rasterUnitsPerPixelY() ) / diagonalSize );
12558     }
12559 
12560     mMapCanvas->refresh();
12561     QgsDebugMsgLevel( "MapUnitsPerPixel after  : " + QString::number( mMapCanvas->mapUnitsPerPixel() ), 2 );
12562   }
12563 }
12564 
legendLayerStretchUsingCurrentExtent()12565 void QgisApp::legendLayerStretchUsingCurrentExtent()
12566 {
12567   if ( !mLayerTreeView )
12568     return;
12569 
12570   //find current Layer
12571   QgsMapLayer *currentLayer = mLayerTreeView->currentLayer();
12572   if ( !currentLayer )
12573     return;
12574 
12575   QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( currentLayer );
12576   if ( layer )
12577   {
12578     QgsRectangle myRectangle;
12579     myRectangle = mMapCanvas->mapSettings().outputExtentToLayerExtent( layer, mMapCanvas->extent() );
12580     layer->refreshContrastEnhancement( myRectangle );
12581 
12582     mLayerTreeView->refreshLayerSymbology( layer->id() );
12583     refreshMapCanvas();
12584   }
12585 }
12586 
applyStyleToGroup()12587 void QgisApp::applyStyleToGroup()
12588 {
12589   if ( !mLayerTreeView )
12590     return;
12591 
12592   const auto constSelectedNodes = mLayerTreeView->selectedNodes();
12593   for ( QgsLayerTreeNode *node : constSelectedNodes )
12594   {
12595     if ( QgsLayerTree::isGroup( node ) )
12596     {
12597       const auto constFindLayers = QgsLayerTree::toGroup( node )->findLayers();
12598       for ( QgsLayerTreeLayer *nodeLayer : constFindLayers )
12599       {
12600         if ( nodeLayer->layer() )
12601         {
12602           pasteStyle( nodeLayer->layer() );
12603         }
12604       }
12605     }
12606     else if ( QgsLayerTree::isLayer( node ) )
12607     {
12608       QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
12609       if ( nodeLayer->layer() )
12610       {
12611         pasteStyle( nodeLayer->layer() );
12612       }
12613     }
12614   }
12615 }
12616 
legendGroupSetCrs()12617 void QgisApp::legendGroupSetCrs()
12618 {
12619   if ( !mMapCanvas )
12620   {
12621     return;
12622   }
12623 
12624   QgsLayerTreeGroup *currentGroup = mLayerTreeView->currentGroupNode();
12625   if ( !currentGroup )
12626     return;
12627 
12628   QgsProjectionSelectionDialog mySelector( this );
12629   if ( !mySelector.exec() )
12630   {
12631     QApplication::restoreOverrideCursor();
12632     return;
12633   }
12634 
12635   QgsCoordinateReferenceSystem crs = mySelector.crs();
12636   const auto constFindLayers = currentGroup->findLayers();
12637   for ( QgsLayerTreeLayer *nodeLayer : constFindLayers )
12638   {
12639     if ( nodeLayer->layer() )
12640     {
12641       nodeLayer->layer()->setCrs( crs );
12642       nodeLayer->layer()->triggerRepaint();
12643     }
12644   }
12645 }
12646 
legendGroupSetWmsData()12647 void QgisApp::legendGroupSetWmsData()
12648 {
12649   QgsLayerTreeGroup *currentGroup = mLayerTreeView->currentGroupNode();
12650   if ( !currentGroup )
12651     return;
12652   QgsGroupWmsDataDialog *dlg = new QgsGroupWmsDataDialog( this );
12653   dlg->setGroupShortName( currentGroup->customProperty( QStringLiteral( "wmsShortName" ) ).toString() );
12654   dlg->setGroupTitle( currentGroup->customProperty( QStringLiteral( "wmsTitle" ) ).toString() );
12655   dlg->setGroupAbstract( currentGroup->customProperty( QStringLiteral( "wmsAbstract" ) ).toString() );
12656   if ( dlg->exec() )
12657   {
12658     currentGroup->setCustomProperty( QStringLiteral( "wmsShortName" ), dlg->groupShortName() );
12659     currentGroup->setCustomProperty( QStringLiteral( "wmsTitle" ), dlg->groupTitle() );
12660     currentGroup->setCustomProperty( QStringLiteral( "wmsAbstract" ), dlg->groupAbstract() );
12661   }
12662   delete dlg;
12663 }
12664 
zoomToLayerExtent()12665 void QgisApp::zoomToLayerExtent()
12666 {
12667   mLayerTreeView->defaultActions()->zoomToLayers( mMapCanvas );
12668 }
12669 
showPluginManager()12670 void QgisApp::showPluginManager()
12671 {
12672 #ifdef WITH_BINDINGS
12673   if ( mPythonUtils && mPythonUtils->isEnabled() )
12674   {
12675     // Call pluginManagerInterface()->showPluginManager() as soon as the plugin installer says the remote data is fetched.
12676     QgsPythonRunner::run( QStringLiteral( "pyplugin_installer.instance().showPluginManagerWhenReady()" ) );
12677   }
12678   else
12679 #endif
12680   {
12681     // Call the pluginManagerInterface directly
12682     mQgisInterface->pluginManagerInterface()->showPluginManager();
12683   }
12684 }
12685 
12686 
12687 // implementation of the python runner
12688 class QgsPythonRunnerImpl : public QgsPythonRunner
12689 {
12690   public:
QgsPythonRunnerImpl(QgsPythonUtils * pythonUtils)12691     explicit QgsPythonRunnerImpl( QgsPythonUtils *pythonUtils ) : mPythonUtils( pythonUtils ) {}
12692 
runCommand(QString command,QString messageOnError=QString ())12693     bool runCommand( QString command, QString messageOnError = QString() ) override
12694     {
12695 #ifdef WITH_BINDINGS
12696       if ( mPythonUtils && mPythonUtils->isEnabled() )
12697       {
12698         return mPythonUtils->runString( command, messageOnError, false );
12699       }
12700 #else
12701       Q_UNUSED( command )
12702       Q_UNUSED( messageOnError )
12703 #endif
12704       return false;
12705     }
12706 
evalCommand(QString command,QString & result)12707     bool evalCommand( QString command, QString &result ) override
12708     {
12709 #ifdef WITH_BINDINGS
12710       if ( mPythonUtils && mPythonUtils->isEnabled() )
12711       {
12712         return mPythonUtils->evalString( command, result );
12713       }
12714 #else
12715       Q_UNUSED( command )
12716       Q_UNUSED( result )
12717 #endif
12718       return false;
12719     }
12720 
12721   protected:
12722     QgsPythonUtils *mPythonUtils = nullptr;
12723 };
12724 
loadPythonSupport()12725 void QgisApp::loadPythonSupport()
12726 {
12727   QgsScopedRuntimeProfile profile( tr( "Loading Python support" ) );
12728 
12729   QString pythonlibName( QStringLiteral( "qgispython" ) );
12730 #if defined(Q_OS_UNIX)
12731   pythonlibName.prepend( QgsApplication::libraryPath() );
12732 #endif
12733 #ifdef __MINGW32__
12734   pythonlibName.prepend( "lib" );
12735 #endif
12736   QString version = QStringLiteral( "%1.%2.%3" ).arg( Qgis::versionInt() / 10000 ).arg( Qgis::versionInt() / 100 % 100 ).arg( Qgis::versionInt() % 100 );
12737   QgsDebugMsgLevel( QStringLiteral( "load library %1 (%2)" ).arg( pythonlibName, version ), 2 );
12738   QLibrary pythonlib( pythonlibName, version );
12739   // It's necessary to set these two load hints, otherwise Python library won't work correctly
12740   // see http://lists.kde.org/?l=pykde&m=117190116820758&w=2
12741   pythonlib.setLoadHints( QLibrary::ResolveAllSymbolsHint | QLibrary::ExportExternalSymbolsHint );
12742   if ( !pythonlib.load() )
12743   {
12744     pythonlib.setFileName( pythonlibName );
12745     if ( !pythonlib.load() )
12746     {
12747       QgsMessageLog::logMessage( tr( "Couldn't load Python support library: %1" ).arg( pythonlib.errorString() ) );
12748       return;
12749     }
12750   }
12751 
12752 #ifdef WITH_BINDINGS
12753   typedef QgsPythonUtils*( *inst )();
12754   inst pythonlib_inst = reinterpret_cast< inst >( cast_to_fptr( pythonlib.resolve( "instance" ) ) );
12755   if ( !pythonlib_inst )
12756   {
12757     //using stderr on purpose because we want end users to see this [TS]
12758     QgsMessageLog::logMessage( tr( "Couldn't resolve python support library's instance() symbol." ) );
12759     return;
12760   }
12761 
12762   mPythonUtils = pythonlib_inst();
12763   if ( mPythonUtils )
12764   {
12765     mPythonUtils->initPython( mQgisInterface, true );
12766   }
12767 
12768   if ( mPythonUtils && mPythonUtils->isEnabled() )
12769   {
12770     QgsPluginRegistry::instance()->setPythonUtils( mPythonUtils );
12771 
12772     // init python runner
12773     QgsPythonRunner::setInstance( new QgsPythonRunnerImpl( mPythonUtils ) );
12774 
12775     // QgsMessageLog::logMessage( tr( "Python support ENABLED :-) " ), QString(), Qgis::MessageLevel::Info );
12776   }
12777 #endif
12778 }
12779 
checkQgisVersion()12780 void QgisApp::checkQgisVersion()
12781 {
12782   QgsVersionInfo *versionInfo = new QgsVersionInfo();
12783   QApplication::setOverrideCursor( Qt::WaitCursor );
12784 
12785   connect( versionInfo, &QgsVersionInfo::versionInfoAvailable, this, &QgisApp::versionReplyFinished );
12786   versionInfo->checkVersion();
12787 }
12788 
versionReplyFinished()12789 void QgisApp::versionReplyFinished()
12790 {
12791   QApplication::restoreOverrideCursor();
12792 
12793   QgsVersionInfo *versionInfo = qobject_cast<QgsVersionInfo *>( sender() );
12794   Q_ASSERT( versionInfo );
12795 
12796   if ( versionInfo->error() == QNetworkReply::NoError )
12797   {
12798     QString info;
12799 
12800     if ( versionInfo->newVersionAvailable() )
12801     {
12802       info = tr( "There is a new version of QGIS available" );
12803     }
12804     else if ( versionInfo->isDevelopmentVersion() )
12805     {
12806       info = tr( "You are running a development version of QGIS" );
12807     }
12808     else
12809     {
12810       info = tr( "You are running the current version of QGIS" );
12811     }
12812 
12813     info = QStringLiteral( "<b>%1</b>" ).arg( info );
12814 
12815     if ( versionInfo->newVersionAvailable() )
12816       info += "<br>" + QgsStringUtils::insertLinks( versionInfo->downloadInfo() );
12817 
12818     QMessageBox mb( QMessageBox::Information, tr( "QGIS Version Information" ), info );
12819     mb.setInformativeText( versionInfo->html() );
12820     mb.exec();
12821   }
12822   else
12823   {
12824     QMessageBox mb( QMessageBox::Warning, tr( "QGIS Version Information" ), tr( "Unable to get current version information from server" ) );
12825     mb.setDetailedText( versionInfo->errorString() );
12826     mb.exec();
12827   }
12828 }
12829 
configureShortcuts()12830 void QgisApp::configureShortcuts()
12831 {
12832   QgsConfigureShortcutsDialog dlg( this );
12833   dlg.exec();
12834 }
12835 
customize()12836 void QgisApp::customize()
12837 {
12838   QgsCustomization::instance()->openDialog( this );
12839 }
12840 
options()12841 void QgisApp::options()
12842 {
12843   showOptionsDialog( this );
12844 }
12845 
projectPropertiesPagesMap()12846 QMap< QString, QString > QgisApp::projectPropertiesPagesMap()
12847 {
12848   static QMap< QString, QString > sProjectPropertiesPagesMap;
12849   static std::once_flag initialized;
12850   std::call_once( initialized, []
12851   {
12852     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "General" ), QStringLiteral( "mProjOptsGeneral" ) );
12853     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "Metadata" ), QStringLiteral( "mMetadataPage" ) );
12854     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "View Settings" ), QStringLiteral( "mViewSettingsPage" ) );
12855     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "CRS" ), QStringLiteral( "mProjOptsCRS" ) );
12856     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "Transformations" ), QStringLiteral( "mProjTransformations" ) );
12857     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "Default Styles" ), QStringLiteral( "mProjOptsSymbols" ) );
12858     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "Data Sources" ), QStringLiteral( "mTab_DataSources" ) );
12859     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "Relations" ), QStringLiteral( "mTabRelations" ) );
12860     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "Variables" ), QStringLiteral( "mTab_Variables" ) );
12861     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "Macros" ), QStringLiteral( "mProjOptsMacros" ) );
12862     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "QGIS Server" ), QStringLiteral( "mProjOptsOWS" ) );
12863     sProjectPropertiesPagesMap.insert( QCoreApplication::translate( "QgsProjectPropertiesBase", "Temporal" ), QStringLiteral( "mTemporalOptions" ) );
12864   } );
12865 
12866   for ( const QPointer< QgsOptionsWidgetFactory > &f : std::as_const( mProjectPropertiesWidgetFactories ) )
12867   {
12868     // remove any deleted factories
12869     if ( f )
12870     {
12871       sProjectPropertiesPagesMap.insert( f->title(), f->title() );
12872     }
12873   }
12874 
12875   return sProjectPropertiesPagesMap;
12876 }
12877 
showProjectProperties(const QString & page)12878 void QgisApp::showProjectProperties( const QString &page )
12879 {
12880   projectProperties( page );
12881 }
12882 
settingPagesMap()12883 QMap< QString, QString > QgisApp::settingPagesMap()
12884 {
12885   static QMap< QString, QString > sSettingPagesMap;
12886   static std::once_flag initialized;
12887   std::call_once( initialized, []
12888   {
12889     sSettingPagesMap.insert( tr( "Style Manager" ), QStringLiteral( "stylemanager" ) );
12890     sSettingPagesMap.insert( tr( "Keyboard Shortcuts" ), QStringLiteral( "shortcuts" ) );
12891     sSettingPagesMap.insert( tr( "Custom Projections" ), QStringLiteral( "customprojection" ) );
12892     sSettingPagesMap.insert( tr( "Interface Customization" ), QStringLiteral( "customize" ) );
12893   } );
12894 
12895   return sSettingPagesMap;
12896 }
12897 
showSettings(const QString & page)12898 void QgisApp::showSettings( const QString &page )
12899 {
12900   if ( page == QLatin1String( "stylemanager" ) )
12901   {
12902     showStyleManager();
12903   }
12904   else if ( page == QLatin1String( "shortcuts" ) )
12905   {
12906     configureShortcuts();
12907   }
12908   else if ( page == QLatin1String( "customprojection" ) )
12909   {
12910     customProjection();
12911   }
12912   else if ( page == QLatin1String( "customize" ) )
12913   {
12914     customize();
12915   }
12916 }
12917 
optionsPagesMap()12918 QMap<QString, QString> QgisApp::optionsPagesMap()
12919 {
12920   static QMap< QString, QString > sOptionsPagesMap;
12921   static std::once_flag initialized;
12922   std::call_once( initialized, []
12923   {
12924     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "General" ), QStringLiteral( "mOptionsPageGeneral" ) );
12925     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "System" ), QStringLiteral( "mOptionsPageSystem" ) );
12926     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "CRS" ), QStringLiteral( "mOptionsPageCRS" ) );
12927     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Transformations" ), QStringLiteral( "mOptionsPageTransformations" ) );
12928     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Data Sources" ), QStringLiteral( "mOptionsPageDataSources" ) );
12929     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "GDAL" ), QStringLiteral( "mOptionsPageGDAL" ) );
12930     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Rendering" ), QStringLiteral( "mOptionsPageRendering" ) );
12931     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Canvas & Legend" ), QStringLiteral( "mOptionsPageMapCanvas" ) );
12932     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Map Tools" ), QStringLiteral( "mOptionsPageMapTools" ) );
12933     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Colors" ), QStringLiteral( "mOptionsPageColors" ) );
12934     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Digitizing" ), QStringLiteral( "mOptionsPageDigitizing" ) );
12935     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Layouts" ), QStringLiteral( "mOptionsPageComposer" ) );
12936     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Variables" ), QStringLiteral( "mOptionsPageVariables" ) );
12937     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Authentication" ), QStringLiteral( "mOptionsPageAuth" ) );
12938     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Network" ), QStringLiteral( "mOptionsPageNetwork" ) );
12939     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Locator" ), QStringLiteral( "mOptionsLocatorSettings" ) );
12940     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Acceleration" ), QStringLiteral( "mOptionsPageAcceleration" ) );
12941     sOptionsPagesMap.insert( QCoreApplication::translate( "QgsOptionsBase", "Advanced" ), QCoreApplication::translate( "QgsOptionsBase", "Advanced" ) );
12942   } );
12943 
12944   QMap< QString, QString > pages = sOptionsPagesMap;
12945   for ( const QPointer< QgsOptionsWidgetFactory > &f : std::as_const( mOptionsWidgetFactories ) )
12946   {
12947     // remove any deleted factories
12948     if ( f )
12949     {
12950       pages.insert( f->title(), f->title() );
12951     }
12952   }
12953   return pages;
12954 }
12955 
createOptionsDialog(QWidget * parent)12956 QgsOptions *QgisApp::createOptionsDialog( QWidget *parent )
12957 {
12958   QList< QgsOptionsWidgetFactory * > factories;
12959   const auto constMOptionsWidgetFactories = mOptionsWidgetFactories;
12960   for ( const QPointer< QgsOptionsWidgetFactory > &f : constMOptionsWidgetFactories )
12961   {
12962     // remove any deleted factories
12963     if ( f )
12964       factories << f;
12965   }
12966   return new QgsOptions( parent, QgsGuiUtils::ModalDialogFlags, factories );
12967 }
12968 
12969 
showOptionsDialog(QWidget * parent,const QString & currentPage,int pageNumber)12970 void QgisApp::showOptionsDialog( QWidget *parent, const QString &currentPage, int pageNumber )
12971 {
12972   std::unique_ptr< QgsOptions > optionsDialog( createOptionsDialog( parent ) );
12973 
12974   QgsSettings mySettings;
12975   QString oldScales = mySettings.value( QStringLiteral( "Map/scales" ), Qgis::defaultProjectScales() ).toString();
12976 
12977   if ( !currentPage.isEmpty() )
12978   {
12979     optionsDialog->setCurrentPage( currentPage );
12980   }
12981 
12982   if ( pageNumber >= 0 )
12983   {
12984     optionsDialog->setCurrentPage( pageNumber );
12985   }
12986 
12987   if ( optionsDialog->exec() )
12988   {
12989     QgsProject::instance()->layerTreeRegistryBridge()->setNewLayersVisible( mySettings.value( QStringLiteral( "qgis/new_layers_visible" ), true ).toBool() );
12990 
12991     setupLayerTreeViewFromSettings();
12992 
12993     const auto canvases = mapCanvases();
12994     for ( QgsMapCanvas *canvas : canvases )
12995     {
12996       applyDefaultSettingsToCanvas( canvas );
12997     }
12998 
12999     //update any open compositions so they reflect new composer settings
13000     //we have to push the changes to the compositions here, because compositions
13001     //have no access to qgisapp and accordingly can't listen in to changes
13002     const QList< QgsMasterLayoutInterface * > layouts = QgsProject::instance()->layoutManager()->layouts() ;
13003     for ( QgsMasterLayoutInterface *layout : layouts )
13004     {
13005       layout->updateSettings();
13006     }
13007 
13008     //do we need this? TS
13009     for ( QgsMapCanvas *canvas : canvases )
13010     {
13011       canvas->refresh();
13012     }
13013 
13014     mRasterFileFilter = QgsProviderRegistry::instance()->fileRasterFilters();
13015 
13016     if ( oldScales != mySettings.value( QStringLiteral( "Map/scales" ), Qgis::defaultProjectScales() ).toString() )
13017     {
13018       mScaleWidget->updateScales();
13019     }
13020 
13021     mMapTools->mapTool< QgsMeasureTool >( QgsAppMapTools::MeasureDistance )->updateSettings();
13022     mMapTools->mapTool< QgsMeasureTool >( QgsAppMapTools::MeasureArea )->updateSettings();
13023     mMapTools->mapTool< QgsMapToolMeasureAngle >( QgsAppMapTools::MeasureAngle )->updateSettings();
13024     mMapTools->mapTool< QgsMapToolMeasureBearing >( QgsAppMapTools::MeasureBearing )->updateSettings();
13025 
13026 #ifdef HAVE_3D
13027     const QList< Qgs3DMapCanvasDockWidget * > canvases3D = findChildren< Qgs3DMapCanvasDockWidget * >();
13028     for ( Qgs3DMapCanvasDockWidget *canvas3D : canvases3D )
13029     {
13030       canvas3D->measurementLineTool()->updateSettings();
13031     }
13032 #endif
13033 
13034     double factor = mySettings.value( QStringLiteral( "qgis/magnifier_factor_default" ), 1.0 ).toDouble();
13035     mMagnifierWidget->setDefaultFactor( factor );
13036     mMagnifierWidget->updateMagnification( factor );
13037 
13038     mWelcomePage->updateNewsFeedVisibility();
13039   }
13040 }
13041 
fullHistogramStretch()13042 void QgisApp::fullHistogramStretch()
13043 {
13044   histogramStretch( false, QgsRasterMinMaxOrigin::MinMax );
13045 }
13046 
localHistogramStretch()13047 void QgisApp::localHistogramStretch()
13048 {
13049   histogramStretch( true, QgsRasterMinMaxOrigin::MinMax );
13050 }
13051 
fullCumulativeCutStretch()13052 void QgisApp::fullCumulativeCutStretch()
13053 {
13054   histogramStretch( false, QgsRasterMinMaxOrigin::CumulativeCut );
13055 }
13056 
localCumulativeCutStretch()13057 void QgisApp::localCumulativeCutStretch()
13058 {
13059   histogramStretch( true, QgsRasterMinMaxOrigin::CumulativeCut );
13060 }
13061 
histogramStretch(bool visibleAreaOnly,QgsRasterMinMaxOrigin::Limits limits)13062 void QgisApp::histogramStretch( bool visibleAreaOnly, QgsRasterMinMaxOrigin::Limits limits )
13063 {
13064   QgsMapLayer *myLayer = mLayerTreeView->currentLayer();
13065 
13066   if ( !myLayer )
13067   {
13068     visibleMessageBar()->pushMessage( tr( "No Layer Selected" ),
13069                                       tr( "To perform a full histogram stretch, you need to have a raster layer selected." ),
13070                                       Qgis::MessageLevel::Info );
13071     return;
13072   }
13073 
13074   QgsRasterLayer *myRasterLayer = qobject_cast<QgsRasterLayer *>( myLayer );
13075   if ( !myRasterLayer )
13076   {
13077     visibleMessageBar()->pushMessage( tr( "No Layer Selected" ),
13078                                       tr( "To perform a full histogram stretch, you need to have a raster layer selected." ),
13079                                       Qgis::MessageLevel::Info );
13080     return;
13081   }
13082 
13083   QgsRectangle myRectangle;
13084   if ( visibleAreaOnly )
13085     myRectangle = mMapCanvas->mapSettings().outputExtentToLayerExtent( myRasterLayer, mMapCanvas->extent() );
13086 
13087   myRasterLayer->setContrastEnhancement( QgsContrastEnhancement::StretchToMinimumMaximum, limits, myRectangle );
13088 
13089   myRasterLayer->triggerRepaint();
13090 }
13091 
increaseBrightness()13092 void QgisApp::increaseBrightness()
13093 {
13094   int step = 1;
13095   if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
13096   {
13097     step = 10;
13098   }
13099   adjustBrightnessContrast( step );
13100 }
13101 
decreaseBrightness()13102 void QgisApp::decreaseBrightness()
13103 {
13104   int step = -1;
13105   if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
13106   {
13107     step = -10;
13108   }
13109   adjustBrightnessContrast( step );
13110 }
13111 
increaseContrast()13112 void QgisApp::increaseContrast()
13113 {
13114   int step = 1;
13115   if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
13116   {
13117     step = 10;
13118   }
13119   adjustBrightnessContrast( step, false );
13120 }
13121 
decreaseContrast()13122 void QgisApp::decreaseContrast()
13123 {
13124   int step = -1;
13125   if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
13126   {
13127     step = -10;
13128   }
13129   adjustBrightnessContrast( step, false );
13130 }
13131 
adjustBrightnessContrast(int delta,bool updateBrightness)13132 void QgisApp::adjustBrightnessContrast( int delta, bool updateBrightness )
13133 {
13134   const auto constSelectedLayers = mLayerTreeView->selectedLayers();
13135   for ( QgsMapLayer *layer : constSelectedLayers )
13136   {
13137     if ( !layer )
13138     {
13139       visibleMessageBar()->pushMessage( tr( "No Layer Selected" ),
13140                                         tr( "To change brightness or contrast, you need to have a raster layer selected." ),
13141                                         Qgis::MessageLevel::Info );
13142       return;
13143     }
13144 
13145     QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer );
13146     if ( !rasterLayer )
13147     {
13148       visibleMessageBar()->pushMessage( tr( "No Layer Selected" ),
13149                                         tr( "To change brightness or contrast, you need to have a raster layer selected." ),
13150                                         Qgis::MessageLevel::Info );
13151       return;
13152     }
13153 
13154     QgsBrightnessContrastFilter *brightnessFilter = rasterLayer->brightnessFilter();
13155 
13156     if ( updateBrightness )
13157     {
13158       brightnessFilter->setBrightness( brightnessFilter->brightness() + delta );
13159     }
13160     else
13161     {
13162       brightnessFilter->setContrast( brightnessFilter->contrast() + delta );
13163     }
13164 
13165     rasterLayer->triggerRepaint();
13166   }
13167 }
13168 
increaseGamma()13169 void QgisApp::increaseGamma()
13170 {
13171   double step = 0.1;
13172   if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
13173   {
13174     step = 1.0;
13175   }
13176   adjustGamma( step );
13177 }
13178 
decreaseGamma()13179 void QgisApp::decreaseGamma()
13180 {
13181   double step = -0.1;
13182   if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
13183   {
13184     step = -1.0;
13185   }
13186   adjustGamma( step );
13187 }
13188 
adjustGamma(double delta)13189 void QgisApp::adjustGamma( double delta )
13190 {
13191   const auto constSelectedLayers = mLayerTreeView->selectedLayers();
13192   for ( QgsMapLayer *layer : constSelectedLayers )
13193   {
13194     if ( !layer )
13195     {
13196       visibleMessageBar()->pushMessage( tr( "No Layer Selected" ),
13197                                         tr( "To change gamma, you need to have a raster layer selected." ),
13198                                         Qgis::MessageLevel::Info );
13199       return;
13200     }
13201 
13202     QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer );
13203     if ( !rasterLayer )
13204     {
13205       visibleMessageBar()->pushMessage( tr( "No Layer Selected" ),
13206                                         tr( "To change gamma, you need to have a raster layer selected." ),
13207                                         Qgis::MessageLevel::Info );
13208       return;
13209     }
13210 
13211     QgsBrightnessContrastFilter *brightnessFilter = rasterLayer->brightnessFilter();
13212     brightnessFilter->setGamma( brightnessFilter->gamma() + delta );
13213 
13214     rasterLayer->triggerRepaint();
13215   }
13216 }
13217 
helpContents()13218 void QgisApp::helpContents()
13219 {
13220   QgsHelp::openHelp( QStringLiteral( "index.html" ) );
13221 }
13222 
apiDocumentation()13223 void QgisApp::apiDocumentation()
13224 {
13225   if ( QFileInfo::exists( QgsApplication::pkgDataPath() + "/doc/api/index.html" ) )
13226   {
13227     openURL( QStringLiteral( "api/index.html" ) );
13228   }
13229   else
13230   {
13231     QgsSettings settings;
13232     QString QgisApiUrl = settings.value( QStringLiteral( "qgis/QgisApiUrl" ),
13233                                          QStringLiteral( "https://qgis.org/api/" ) ).toString();
13234     openURL( QgisApiUrl, false );
13235   }
13236 }
13237 
reportaBug()13238 void QgisApp::reportaBug()
13239 {
13240   QgsSettings settings;
13241   QString reportaBugUrl = settings.value( QStringLiteral( "qgis/reportaBugUrl" ),
13242                                           tr( "https://qgis.org/en/site/getinvolved/development/bugreporting.html" ) ).toString();
13243   openURL( reportaBugUrl, false );
13244 }
13245 
supportProviders()13246 void QgisApp::supportProviders()
13247 {
13248   QgsSettings settings;
13249   QString supportProvidersUrl = settings.value( QStringLiteral( "qgis/supportProvidersUrl" ),
13250                                 tr( "https://qgis.org/en/site/forusers/commercial_support.html" ) ).toString();
13251   openURL( supportProvidersUrl, false );
13252 }
13253 
helpQgisHomePage()13254 void QgisApp::helpQgisHomePage()
13255 {
13256   QgsSettings settings;
13257   QString  helpQgisHomePageUrl = settings.value( QStringLiteral( "qgis/helpQgisHomePageUrl" ),
13258                                  QStringLiteral( "https://qgis.org" ) ).toString();
13259   openURL( helpQgisHomePageUrl, false );
13260 }
13261 
openURL(QString url,bool useQgisDocDirectory)13262 void QgisApp::openURL( QString url, bool useQgisDocDirectory )
13263 {
13264   // open help in user browser
13265   if ( useQgisDocDirectory )
13266   {
13267     url = "file://" + QgsApplication::pkgDataPath() + "/doc/" + url;
13268   }
13269 #ifdef Q_OS_MACX
13270   /* Use Mac OS X Launch Services which uses the user's default browser
13271    * and will just open a new window if that browser is already running.
13272    * QProcess creates a new browser process for each invocation and expects a
13273    * commandline application rather than a bundled application.
13274    */
13275   CFURLRef urlRef = CFURLCreateWithBytes( kCFAllocatorDefault,
13276                                           reinterpret_cast<const UInt8 *>( url.toUtf8().constData() ), url.length(),
13277                                           kCFStringEncodingUTF8, nullptr );
13278   OSStatus status = LSOpenCFURLRef( urlRef, nullptr );
13279   status = 0; //avoid compiler warning
13280   CFRelease( urlRef );
13281 #elif defined(Q_OS_WIN)
13282   if ( url.startsWith( "file://", Qt::CaseInsensitive ) )
13283     ShellExecute( 0, 0, url.mid( 7 ).toLocal8Bit().constData(), 0, 0, SW_SHOWNORMAL );
13284   else
13285     QDesktopServices::openUrl( url );
13286 #else
13287   QDesktopServices::openUrl( url );
13288 #endif
13289 }
13290 
registerMapLayerPropertiesFactory(QgsMapLayerConfigWidgetFactory * factory)13291 void QgisApp::registerMapLayerPropertiesFactory( QgsMapLayerConfigWidgetFactory *factory )
13292 {
13293   mMapLayerPanelFactories << factory;
13294   if ( mMapStyleWidget )
13295     mMapStyleWidget->setPageFactories( mMapLayerPanelFactories );
13296 }
13297 
unregisterMapLayerPropertiesFactory(QgsMapLayerConfigWidgetFactory * factory)13298 void QgisApp::unregisterMapLayerPropertiesFactory( QgsMapLayerConfigWidgetFactory *factory )
13299 {
13300   mMapLayerPanelFactories.removeAll( factory );
13301   if ( mMapStyleWidget )
13302     mMapStyleWidget->setPageFactories( mMapLayerPanelFactories );
13303 }
13304 
registerOptionsWidgetFactory(QgsOptionsWidgetFactory * factory)13305 void QgisApp::registerOptionsWidgetFactory( QgsOptionsWidgetFactory *factory )
13306 {
13307   mOptionsWidgetFactories << factory;
13308 }
13309 
unregisterOptionsWidgetFactory(QgsOptionsWidgetFactory * factory)13310 void QgisApp::unregisterOptionsWidgetFactory( QgsOptionsWidgetFactory *factory )
13311 {
13312   mOptionsWidgetFactories.removeAll( factory );
13313 }
13314 
registerProjectPropertiesWidgetFactory(QgsOptionsWidgetFactory * factory)13315 void QgisApp::registerProjectPropertiesWidgetFactory( QgsOptionsWidgetFactory *factory )
13316 {
13317   mProjectPropertiesWidgetFactories << factory;
13318 }
13319 
unregisterProjectPropertiesWidgetFactory(QgsOptionsWidgetFactory * factory)13320 void QgisApp::unregisterProjectPropertiesWidgetFactory( QgsOptionsWidgetFactory *factory )
13321 {
13322   mProjectPropertiesWidgetFactories.removeAll( factory );
13323 }
13324 
registerDevToolFactory(QgsDevToolWidgetFactory * factory)13325 void QgisApp::registerDevToolFactory( QgsDevToolWidgetFactory *factory )
13326 {
13327   mDevToolFactories << factory;
13328   if ( mDevToolsWidget )
13329   {
13330     // widget was already created, so we manually need to push this factory to the widget
13331     mDevToolsWidget->addToolFactory( factory );
13332   }
13333 }
13334 
unregisterDevToolFactory(QgsDevToolWidgetFactory * factory)13335 void QgisApp::unregisterDevToolFactory( QgsDevToolWidgetFactory *factory )
13336 {
13337   mDevToolsWidget->removeToolFactory( factory );
13338   mDevToolFactories.removeAll( factory );
13339 }
13340 
registerApplicationExitBlocker(QgsApplicationExitBlockerInterface * blocker)13341 void QgisApp::registerApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
13342 {
13343   mApplicationExitBlockers << blocker;
13344 }
13345 
unregisterApplicationExitBlocker(QgsApplicationExitBlockerInterface * blocker)13346 void QgisApp::unregisterApplicationExitBlocker( QgsApplicationExitBlockerInterface *blocker )
13347 {
13348   mApplicationExitBlockers.removeAll( blocker );
13349 }
13350 
registerMapToolHandler(QgsAbstractMapToolHandler * handler)13351 void QgisApp::registerMapToolHandler( QgsAbstractMapToolHandler *handler )
13352 {
13353   if ( !handler->action() || !handler->mapTool() )
13354   {
13355     QgsMessageLog::logMessage( tr( "Map tool handler is not properly constructed" ) );
13356     return;
13357   }
13358 
13359   mMapToolHandlers << handler;
13360 
13361   // do setup work
13362   handler->action()->setCheckable( true );
13363   handler->mapTool()->setAction( handler->action() );
13364 
13365   connect( handler->action(), &QAction::triggered, this, &QgisApp::switchToMapToolViaHandler );
13366   mMapToolGroup->addAction( handler->action() );
13367   QgsAbstractMapToolHandler::Context context;
13368   handler->action()->setEnabled( handler->isCompatibleWithLayer( activeLayer(), context ) );
13369 }
13370 
switchToMapToolViaHandler()13371 void QgisApp::switchToMapToolViaHandler()
13372 {
13373   QAction *sourceAction = qobject_cast< QAction * >( sender() );
13374   if ( !sourceAction )
13375     return;
13376 
13377   QgsAbstractMapToolHandler *handler = nullptr;
13378   for ( QgsAbstractMapToolHandler *h : std::as_const( mMapToolHandlers ) )
13379   {
13380     if ( h->action() == sourceAction )
13381     {
13382       handler = h;
13383       break;
13384     }
13385   }
13386 
13387   if ( !handler )
13388     return;
13389 
13390   if ( mMapCanvas->mapTool() == handler->mapTool() )
13391     return; // nothing to do
13392 
13393   handler->setLayerForTool( activeLayer() );
13394   mMapCanvas->setMapTool( handler->mapTool() );
13395 }
13396 
unregisterMapToolHandler(QgsAbstractMapToolHandler * handler)13397 void QgisApp::unregisterMapToolHandler( QgsAbstractMapToolHandler *handler )
13398 {
13399   mMapToolHandlers.removeAll( handler );
13400 
13401   if ( !handler->action() || !handler->mapTool() )
13402   {
13403     return;
13404   }
13405 
13406   mMapToolGroup->removeAction( handler->action() );
13407   disconnect( handler->action(), &QAction::triggered, this, &QgisApp::switchToMapToolViaHandler );
13408 }
13409 
activeLayer()13410 QgsMapLayer *QgisApp::activeLayer()
13411 {
13412   return mLayerTreeView ? mLayerTreeView->currentLayer() : nullptr;
13413 }
13414 
iconSize(bool dockedToolbar) const13415 QSize QgisApp::iconSize( bool dockedToolbar ) const
13416 {
13417   return QgsGuiUtils::iconSize( dockedToolbar );
13418 }
13419 
setActiveLayer(QgsMapLayer * layer)13420 bool QgisApp::setActiveLayer( QgsMapLayer *layer )
13421 {
13422   if ( !layer )
13423     return false;
13424 
13425   if ( !mLayerTreeView->layerTreeModel()->rootGroup()->findLayer( layer->id() ) )
13426     return false;
13427 
13428   mLayerTreeView->setCurrentLayer( layer );
13429   return true;
13430 }
13431 
reloadConnections()13432 void QgisApp::reloadConnections()
13433 {
13434   emit connectionsChanged( );
13435 }
13436 
showLayoutManager()13437 void QgisApp::showLayoutManager()
13438 {
13439   static_cast< QgsAppWindowManager * >( QgsGui::windowManager() )->openApplicationDialog( QgsAppWindowManager::DialogLayoutManager );
13440 }
13441 
addVectorLayer(const QString & vectorLayerPath,const QString & name,const QString & providerKey)13442 QgsVectorLayer *QgisApp::addVectorLayer( const QString &vectorLayerPath, const QString &name, const QString &providerKey )
13443 {
13444   return addLayerPrivate< QgsVectorLayer >( QgsMapLayerType::VectorLayer, vectorLayerPath, name, !providerKey.isEmpty() ? providerKey : QLatin1String( "ogr" ), true );
13445 }
13446 
13447 template<typename T>
addLayerPrivate(QgsMapLayerType type,const QString & uri,const QString & name,const QString & providerKey,bool guiWarnings)13448 T *QgisApp::addLayerPrivate( QgsMapLayerType type, const QString &uri, const QString &name, const QString &providerKey, bool guiWarnings )
13449 {
13450   QgsSettings settings;
13451 
13452   QgsCanvasRefreshBlocker refreshBlocker;
13453 
13454   QString baseName = settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() ? QgsMapLayer::formatLayerName( name ) : name;
13455 
13456   // if the layer needs authentication, ensure the master password is set
13457   const QRegularExpression rx( "authcfg=([a-z]|[A-Z]|[0-9]){7}" );
13458   if ( rx.match( uri ).hasMatch() )
13459   {
13460     if ( !QgsAuthGuiUtils::isDisabled( messageBar() ) )
13461     {
13462       QgsApplication::authManager()->setMasterPassword( true );
13463     }
13464   }
13465 
13466   QVariantMap uriElements = QgsProviderRegistry::instance()->decodeUri( providerKey, uri );
13467   QString path = uri;
13468   if ( uriElements.contains( QStringLiteral( "path" ) ) )
13469   {
13470     // run layer path through QgsPathResolver so that all inbuilt paths and other localised paths are correctly expanded
13471     path = QgsPathResolver().readPath( uriElements.value( QStringLiteral( "path" ) ).toString() );
13472     uriElements[ QStringLiteral( "path" ) ] = path;
13473   }
13474   // Not all providers implement decodeUri(), so use original uri if uriElements is empty
13475   const QString updatedUri = uriElements.isEmpty() ? uri : QgsProviderRegistry::instance()->encodeUri( providerKey, uriElements );
13476 
13477   const bool canQuerySublayers = QgsProviderRegistry::instance()->providerMetadata( providerKey ) &&
13478                                  ( QgsProviderRegistry::instance()->providerMetadata( providerKey )->capabilities() & QgsProviderMetadata::QuerySublayers );
13479 
13480   T *result = nullptr;
13481   if ( canQuerySublayers )
13482   {
13483     // query sublayers
13484     QList< QgsProviderSublayerDetails > sublayers = QgsProviderRegistry::instance()->providerMetadata( providerKey ) ?
13485         QgsProviderRegistry::instance()->providerMetadata( providerKey )->querySublayers( updatedUri, Qgis::SublayerQueryFlag::IncludeSystemTables )
13486         : QgsProviderRegistry::instance()->querySublayers( updatedUri );
13487 
13488     // filter out non-matching sublayers
13489     sublayers.erase( std::remove_if( sublayers.begin(), sublayers.end(), [type]( const QgsProviderSublayerDetails & sublayer )
13490     {
13491       return sublayer.type() != type;
13492     } ), sublayers.end() );
13493 
13494     if ( sublayers.empty() )
13495     {
13496       if ( guiWarnings )
13497       {
13498         QString msg = tr( "%1 is not a valid or recognized data source." ).arg( uri );
13499         visibleMessageBar()->pushMessage( tr( "Invalid Data Source" ), msg, Qgis::MessageLevel::Critical );
13500       }
13501 
13502       // since the layer is bad, stomp on it
13503       return nullptr;
13504     }
13505     else if ( sublayers.size() > 1 || QgsProviderUtils::sublayerDetailsAreIncomplete( sublayers, QgsProviderUtils::SublayerCompletenessFlag::IgnoreUnknownFeatureCount ) )
13506     {
13507       // ask user for sublayers (unless user settings dictate otherwise!)
13508       switch ( shouldAskUserForSublayers( sublayers ) )
13509       {
13510         case SublayerHandling::AskUser:
13511         {
13512           QgsProviderSublayersDialog dlg( updatedUri, path, sublayers, {type}, this );
13513           if ( dlg.exec() )
13514           {
13515             const QList< QgsProviderSublayerDetails > selectedLayers = dlg.selectedLayers();
13516             if ( !selectedLayers.isEmpty() )
13517             {
13518               result = qobject_cast< T * >( addSublayers( selectedLayers, baseName, dlg.groupName() ).value( 0 ) );
13519             }
13520           }
13521           break;
13522         }
13523         case SublayerHandling::LoadAll:
13524         {
13525           result = qobject_cast< T * >( addSublayers( sublayers, baseName, QString() ).value( 0 ) );
13526           break;
13527         }
13528         case SublayerHandling::AbortLoading:
13529           break;
13530       };
13531     }
13532     else
13533     {
13534       result = qobject_cast< T * >( addSublayers( sublayers, name, QString() ).value( 0 ) );
13535 
13536       if ( result )
13537       {
13538         QString base( baseName );
13539         if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
13540         {
13541           base = QgsMapLayer::formatLayerName( base );
13542         }
13543         result->setName( base );
13544       }
13545     }
13546   }
13547   else
13548   {
13549     QgsMapLayerFactory::LayerOptions options( QgsProject::instance()->transformContext() );
13550     options.loadDefaultStyle = false;
13551     result = qobject_cast< T * >( QgsMapLayerFactory::createLayer( uri, name, type, options, providerKey ) );
13552     if ( result )
13553     {
13554       QString base( baseName );
13555       if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
13556       {
13557         base = QgsMapLayer::formatLayerName( base );
13558       }
13559       result->setName( base );
13560       QgsProject::instance()->addMapLayer( result );
13561 
13562       askUserForDatumTransform( result->crs(), QgsProject::instance()->crs(), result );
13563       postProcessAddedLayer( result );
13564     }
13565   }
13566 
13567   activateDeactivateLayerRelatedActions( activeLayer() );
13568   return result;
13569 }
13570 
addMapLayer(QgsMapLayer * mapLayer)13571 void QgisApp::addMapLayer( QgsMapLayer *mapLayer )
13572 {
13573   QgsCanvasRefreshBlocker refreshBlocker;
13574 
13575   if ( mapLayer->isValid() )
13576   {
13577     // Register this layer with the layers registry
13578     QList<QgsMapLayer *> myList;
13579     myList << mapLayer;
13580     QgsProject::instance()->addMapLayers( myList );
13581 
13582     askUserForDatumTransform( mapLayer->crs(), QgsProject::instance()->crs(), mapLayer );
13583   }
13584   else
13585   {
13586     QString msg = tr( "The layer is not a valid layer and can not be added to the map" );
13587     visibleMessageBar()->pushMessage( tr( "Layer is not valid" ), msg, Qgis::MessageLevel::Critical );
13588   }
13589 }
13590 
13591 
embedLayers()13592 void QgisApp::embedLayers()
13593 {
13594   //dialog to select groups/layers from other project files
13595   QgsProjectLayerGroupDialog d( this );
13596   if ( d.exec() == QDialog::Accepted && d.isValid() )
13597   {
13598     addEmbeddedItems( d.selectedProjectFile(), d.selectedGroups(), d.selectedLayerIds() );
13599   }
13600 }
13601 
addEmbeddedItems(const QString & projectFile,const QStringList & groups,const QStringList & layerIds)13602 void QgisApp::addEmbeddedItems( const QString &projectFile, const QStringList &groups, const QStringList &layerIds )
13603 {
13604   QgsCanvasRefreshBlocker refreshBlocker;
13605 
13606   //groups
13607   QStringList::const_iterator groupIt = groups.constBegin();
13608   for ( ; groupIt != groups.constEnd(); ++groupIt )
13609   {
13610     QgsLayerTreeGroup *newGroup = QgsProject::instance()->createEmbeddedGroup( *groupIt, projectFile, QStringList() );
13611 
13612     if ( newGroup )
13613       QgsProject::instance()->layerTreeRoot()->addChildNode( newGroup );
13614   }
13615 
13616   //layer ids
13617   QList<QDomNode> brokenNodes;
13618 
13619   // resolve dependencies
13620   QgsLayerDefinition::DependencySorter depSorter( projectFile );
13621   QStringList sortedIds = depSorter.sortedLayerIds();
13622   const auto constSortedIds = sortedIds;
13623   for ( const QString &id : constSortedIds )
13624   {
13625     const auto constLayerIds = layerIds;
13626     for ( const QString &selId : constLayerIds )
13627     {
13628       if ( selId == id )
13629         QgsProject::instance()->createEmbeddedLayer( selId, projectFile, brokenNodes );
13630     }
13631   }
13632 
13633   // fix broken relations and dependencies
13634   for ( const QString &id : constSortedIds )
13635   {
13636     QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( id ) );
13637     if ( vlayer )
13638       vectorLayerStyleLoaded( vlayer, QgsMapLayer::AllStyleCategories );
13639   }
13640 
13641   // Resolve references to other layers
13642   const QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
13643   for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
13644   {
13645     it.value()->resolveReferences( QgsProject::instance() );
13646   }
13647 }
13648 
newMapCanvas()13649 void QgisApp::newMapCanvas()
13650 {
13651   int i = 1;
13652 
13653   bool existing = true;
13654   QList< QgsMapCanvas * > existingCanvases = mapCanvases();
13655   QString name;
13656   while ( existing )
13657   {
13658     name = tr( "Map %1" ).arg( i++ );
13659     existing = false;
13660     const auto constExistingCanvases = existingCanvases;
13661     for ( QgsMapCanvas *canvas : constExistingCanvases )
13662     {
13663       if ( canvas->objectName() == name )
13664       {
13665         existing = true;
13666         break;
13667       }
13668     }
13669   }
13670 
13671   QgsMapCanvasDockWidget *dock = createNewMapCanvasDock( name );
13672   if ( dock )
13673   {
13674     setupDockWidget( dock, true );
13675     dock->mapCanvas()->setLayers( mMapCanvas->layers() );
13676     dock->mapCanvas()->setExtent( mMapCanvas->extent() );
13677     QgsDebugMsgLevel( QStringLiteral( "QgisApp::newMapCanvas() -4- : QgsProject::instance()->crs().description[%1] ellipsoid[%2]" ).arg( QgsProject::instance()->crs().description(), QgsProject::instance()->crs().ellipsoidAcronym() ), 3 );
13678     dock->mapCanvas()->setDestinationCrs( QgsProject::instance()->crs() );
13679     dock->mapCanvas()->freeze( false );
13680   }
13681 }
13682 
init3D()13683 void QgisApp::init3D()
13684 {
13685 #ifdef HAVE_3D
13686   // initialize 3D registries
13687   Qgs3D::initialize();
13688   Qgs3DAppUtils::initialize();
13689 #else
13690   mActionNew3DMapCanvas->setVisible( false );
13691 #endif
13692 }
13693 
initNativeProcessing()13694 void QgisApp::initNativeProcessing()
13695 {
13696   QgsApplication::processingRegistry()->addProvider( new QgsNativeAlgorithms( QgsApplication::processingRegistry() ) );
13697 #ifdef HAVE_3D
13698   QgsApplication::processingRegistry()->addProvider( new Qgs3DAlgorithms( QgsApplication::processingRegistry() ) );
13699 #endif
13700 }
13701 
initLayouts()13702 void QgisApp::initLayouts()
13703 {
13704   QgsLayoutGuiUtils::registerGuiForKnownItemTypes( mMapCanvas );
13705 
13706   // 3D map item
13707 #ifdef HAVE_3D
13708   QgsApplication::layoutItemRegistry()->addLayoutItemType(
13709     new QgsLayoutItemMetadata( QgsLayoutItemRegistry::Layout3DMap, QObject::tr( "3D Map" ), QObject::tr( "3D Maps" ), QgsLayoutItem3DMap::create )
13710   );
13711 
13712   auto createRubberBand = ( []( QgsLayoutView * view )->QgsLayoutViewRubberBand *
13713   {
13714     return new QgsLayoutViewRectangularRubberBand( view );
13715   } );
13716   std::unique_ptr< QgsLayoutItemGuiMetadata > map3dMetadata = std::make_unique< QgsLayoutItemGuiMetadata>(
13717         QgsLayoutItemRegistry::Layout3DMap, QObject::tr( "3D Map" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd3DMap.svg" ) ),
13718         [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
13719   {
13720     return new QgsLayout3DMapWidget( qobject_cast< QgsLayoutItem3DMap * >( item ) );
13721   }, createRubberBand );
13722   QgsGui::layoutItemGuiRegistry()->addLayoutItemGuiMetadata( map3dMetadata.release() );
13723 #endif
13724 
13725   mLayoutQptDropHandler = new QgsLayoutQptDropHandler( this );
13726   registerCustomLayoutDropHandler( mLayoutQptDropHandler );
13727   mLayoutImageDropHandler = new QgsLayoutImageDropHandler( this );
13728   registerCustomLayoutDropHandler( mLayoutImageDropHandler );
13729 }
13730 
new3DMapCanvas()13731 void QgisApp::new3DMapCanvas()
13732 {
13733 #ifdef HAVE_3D
13734 
13735   // initialize from project
13736   QgsProject *prj = QgsProject::instance();
13737   QgsRectangle fullExtent = mMapCanvas->projectExtent();
13738 
13739   // some layers may go crazy and make full extent unusable
13740   // we can't go any further - invalid extent would break everything
13741   if ( fullExtent.isEmpty() || !fullExtent.isFinite() )
13742   {
13743     QMessageBox::warning( this, tr( "New 3D Map View" ), tr( "Project extent is not valid. Please add or activate a layer to render." ) );
13744     return;
13745   }
13746 
13747   if ( mMapCanvas->mapSettings().destinationCrs().isGeographic() )
13748   {
13749     QMessageBox::warning( this, tr( "New 3D Map View" ), tr( "3D view currently does not support unprojected coordinate reference systems (CRS).\nPlease switch project's CRS to a projected CRS." ) );
13750     return;
13751   }
13752 
13753   int i = 1;
13754 
13755   bool existing = true;
13756   const QList< Qgs3DMapCanvas * > existingCanvases = findChildren< Qgs3DMapCanvas * >();
13757   QString name;
13758   while ( existing )
13759   {
13760     name = tr( "3D Map %1" ).arg( i++ );
13761     existing = false;
13762     for ( Qgs3DMapCanvas *canvas : existingCanvases )
13763     {
13764       if ( canvas->objectName() == name )
13765       {
13766         existing = true;
13767         break;
13768       }
13769     }
13770   }
13771 
13772   Qgs3DMapCanvasDockWidget *dock = createNew3DMapCanvasDock( name );
13773   if ( dock )
13774   {
13775     setupDockWidget( dock, true );
13776 
13777     QgsSettings settings;
13778 
13779     Qgs3DMapSettings *map = new Qgs3DMapSettings;
13780     map->setCrs( prj->crs() );
13781     map->setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) );
13782     map->setSelectionColor( mMapCanvas->selectionColor() );
13783     map->setBackgroundColor( mMapCanvas->canvasColor() );
13784     map->setLayers( mMapCanvas->layers() );
13785 //    map->setTerrainLayers( mMapCanvas->layers() );
13786     map->setTemporalRange( mMapCanvas->temporalRange() );
13787 
13788     const QgsCameraController::NavigationMode defaultNavMode = settings.enumValue( QStringLiteral( "map3d/defaultNavigation" ), QgsCameraController::TerrainBasedNavigation, QgsSettings::App );
13789     map->setCameraNavigationMode( defaultNavMode );
13790 
13791     map->setCameraMovementSpeed( settings.value( QStringLiteral( "map3d/defaultMovementSpeed" ), 5, QgsSettings::App ).toDouble() );
13792     const Qt3DRender::QCameraLens::ProjectionType defaultProjection = settings.enumValue( QStringLiteral( "map3d/defaultProjection" ), Qt3DRender::QCameraLens::PerspectiveProjection, QgsSettings::App );
13793     map->setProjectionType( defaultProjection );
13794     map->setFieldOfView( settings.value( QStringLiteral( "map3d/defaultFieldOfView" ), 45, QgsSettings::App ).toInt() );
13795 
13796     map->setTransformContext( QgsProject::instance()->transformContext() );
13797     map->setPathResolver( QgsProject::instance()->pathResolver() );
13798     map->setMapThemeCollection( QgsProject::instance()->mapThemeCollection() );
13799     connect( QgsProject::instance(), &QgsProject::transformContextChanged, map, [map]
13800     {
13801       map->setTransformContext( QgsProject::instance()->transformContext() );
13802     } );
13803 
13804     QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
13805     flatTerrain->setCrs( map->crs() );
13806     flatTerrain->setExtent( fullExtent );
13807     map->setTerrainGenerator( flatTerrain );
13808 
13809     // new scenes default to a single directional light
13810     map->setDirectionalLights( QList<QgsDirectionalLightSettings>() << QgsDirectionalLightSettings() );
13811     map->setOutputDpi( QgsApplication::desktop()->logicalDpiX() );
13812 
13813     dock->setMapSettings( map );
13814 
13815     QgsRectangle extent = mMapCanvas->extent();
13816     float dist = static_cast< float >( std::max( extent.width(), extent.height() ) );
13817     dock->mapCanvas3D()->setViewFromTop( mMapCanvas->extent().center(), dist, static_cast< float >( mMapCanvas->rotation() ) );
13818 
13819     const QgsCameraController::VerticalAxisInversion axisInversion = settings.enumValue( QStringLiteral( "map3d/axisInversion" ), QgsCameraController::WhenDragging, QgsSettings::App );
13820     if ( dock->mapCanvas3D()->cameraController() )
13821       dock->mapCanvas3D()->cameraController()->setVerticalAxisInversion( axisInversion );
13822   }
13823 #endif
13824 }
13825 
createNew3DMapCanvasDock(const QString & name)13826 Qgs3DMapCanvasDockWidget *QgisApp::createNew3DMapCanvasDock( const QString &name )
13827 {
13828 #ifdef HAVE_3D
13829   const QList<Qgs3DMapCanvas *> mapCanvases = findChildren<Qgs3DMapCanvas *>();
13830   for ( Qgs3DMapCanvas *canvas : mapCanvases )
13831   {
13832     if ( canvas->objectName() == name )
13833     {
13834       QgsDebugMsg( QStringLiteral( "A map canvas with name '%1' already exists!" ).arg( name ) );
13835       return nullptr;
13836     }
13837   }
13838 
13839   markDirty();
13840 
13841   Qgs3DMapCanvasDockWidget *map3DWidget = new Qgs3DMapCanvasDockWidget( this );
13842   map3DWidget->setAllowedAreas( Qt::AllDockWidgetAreas );
13843   map3DWidget->setWindowTitle( name );
13844   map3DWidget->mapCanvas3D()->setObjectName( name );
13845   map3DWidget->setMainCanvas( mMapCanvas );
13846   map3DWidget->mapCanvas3D()->setTemporalController( mTemporalControllerWidget->temporalController() );
13847   return map3DWidget;
13848 #else
13849   Q_UNUSED( name )
13850   return nullptr;
13851 #endif
13852 }
13853 
setExtent(const QgsRectangle & rect)13854 void QgisApp::setExtent( const QgsRectangle &rect )
13855 {
13856   mMapCanvas->setExtent( rect );
13857 }
13858 
saveDirty()13859 bool QgisApp::saveDirty()
13860 {
13861   QString whyDirty;
13862   bool hasUnsavedEdits = false;
13863   // extra check to see if there are any vector layers with unsaved provider edits
13864   // to ensure user has opportunity to save any editing
13865   if ( QgsProject::instance()->count() > 0 )
13866   {
13867     QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
13868     for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
13869     {
13870       QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
13871       // note that we skip the unsaved edits check for memory layers -- it's misleading, because their contents aren't actually
13872       // saved if this is part of a project close operation. Instead we let these get picked up by checkMemoryLayers().
13873       if ( !vl || vl->providerType() == QLatin1String( "memory" ) )
13874       {
13875         continue;
13876       }
13877 
13878       hasUnsavedEdits = ( vl->isEditable() && vl->isModified() );
13879       if ( hasUnsavedEdits )
13880       {
13881         break;
13882       }
13883     }
13884 
13885     if ( hasUnsavedEdits )
13886     {
13887       markDirty();
13888       whyDirty = QStringLiteral( "<p style='color:darkred;'>" );
13889       whyDirty += tr( "Project has layer(s) in edit mode with unsaved edits, which will NOT be saved!" );
13890       whyDirty += QLatin1String( "</p>" );
13891     }
13892   }
13893 
13894   QMessageBox::StandardButton answer( QMessageBox::Discard );
13895   QgsCanvasRefreshBlocker refreshBlocker;
13896 
13897   QgsSettings settings;
13898   bool askThem = settings.value( QStringLiteral( "qgis/askToSaveProjectChanges" ), true ).toBool();
13899 
13900   if ( askThem && QgsProject::instance()->isDirty() )
13901   {
13902     // flag project as dirty since dirty state of canvas is reset if "dirty"
13903     // is based on a zoom or pan
13904     markDirty();
13905 
13906     // prompt user to save
13907     answer = QMessageBox::question( this, tr( "Save Project" ),
13908                                     tr( "Do you want to save the current project? %1" )
13909                                     .arg( whyDirty ),
13910                                     QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard,
13911                                     hasUnsavedEdits ? QMessageBox::Cancel : QMessageBox::Save );
13912     if ( QMessageBox::Save == answer )
13913     {
13914       if ( !fileSave() )
13915         answer = QMessageBox::Cancel;
13916     }
13917   }
13918 
13919   if ( answer == QMessageBox::Cancel )
13920     return false;
13921 
13922   // for memory layers, we discard all unsaved changes manually. Users have already been warned about
13923   // these by an earlier call to checkMemoryLayers(), and we don't want duplicate "unsaved changes" prompts
13924   // and anyway, saving the changes to a memory layer here won't actually save ANYTHING!
13925   // we do this at the very end here, because if the user opted to cancel above then ALL unsaved
13926   // changes in memory layers should still exist for them.
13927   const QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
13928   for ( auto it = layers.begin(); it != layers.end(); ++it )
13929   {
13930     if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() ) )
13931     {
13932       if ( vl->providerType() == QLatin1String( "memory" ) && vl->isEditable() && vl->isModified() )
13933       {
13934         vl->rollBack();
13935       }
13936     }
13937   }
13938 
13939   return true;
13940 }
13941 
checkUnsavedLayerEdits()13942 bool QgisApp::checkUnsavedLayerEdits()
13943 {
13944   // check to see if there are any vector layers with unsaved provider edits
13945   // to ensure user has opportunity to save any editing
13946   if ( QgsProject::instance()->count() > 0 )
13947   {
13948     const QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
13949     for ( auto it = layers.begin(); it != layers.end(); ++it )
13950     {
13951       if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() ) )
13952       {
13953         // note that we skip the unsaved edits check for memory layers -- it's misleading, because their contents aren't actually
13954         // saved if this is part of a project close operation. Instead we let these get picked up by checkMemoryLayers()
13955         if ( ! vl->dataProvider() || vl->providerType() == QLatin1String( "memory" ) )
13956           continue;
13957 
13958         const bool hasUnsavedEdits = ( vl->isEditable() && vl->isModified() );
13959         if ( !hasUnsavedEdits )
13960           continue;
13961 
13962         if ( !toggleEditing( vl, true ) )
13963           return false;
13964       }
13965     }
13966   }
13967 
13968   return true;
13969 }
13970 
checkMemoryLayers()13971 bool QgisApp::checkMemoryLayers()
13972 {
13973   if ( !QgsSettings().value( QStringLiteral( "askToSaveMemoryLayers" ), true, QgsSettings::App ).toBool() )
13974     return true;
13975 
13976   // check to see if there are any temporary layers present (with features)
13977   bool hasTemporaryLayers = false;
13978   bool hasMemoryLayers = false;
13979 
13980   const QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
13981   for ( auto it = layers.begin(); it != layers.end(); ++it )
13982   {
13983     if ( it.value() && it.value()->providerType() == QLatin1String( "memory" ) )
13984     {
13985       QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() );
13986       if ( vl && vl->featureCount() != 0 && !vl->customProperty( QStringLiteral( "skipMemoryLayersCheck" ) ).toInt() )
13987       {
13988         hasMemoryLayers = true;
13989         break;
13990       }
13991     }
13992     else if ( it.value() && it.value()->isTemporary() )
13993     {
13994       hasTemporaryLayers = true;
13995     }
13996   }
13997 
13998   bool close = true;
13999   if ( hasTemporaryLayers )
14000     close &= QMessageBox::warning( this,
14001                                    tr( "Close Project" ),
14002                                    tr( "This project includes one or more temporary layers. These layers are not permanently saved and their contents will be lost. Are you sure you want to proceed?" ),
14003                                    QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel ) == QMessageBox::Yes ;
14004   else if ( hasMemoryLayers )
14005     // use the more specific warning for memory layers
14006     close &= QMessageBox::warning( this,
14007                                    tr( "Close Project" ),
14008                                    tr( "This project includes one or more temporary scratch layers. These layers are not saved to disk and their contents will be permanently lost. Are you sure you want to proceed?" ),
14009                                    QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel ) == QMessageBox::Yes;
14010 
14011   return close;
14012 }
14013 
checkExitBlockers()14014 bool QgisApp::checkExitBlockers()
14015 {
14016   for ( QgsApplicationExitBlockerInterface *blocker : std::as_const( mApplicationExitBlockers ) )
14017   {
14018     if ( !blocker->allowExit() )
14019       return false;
14020   }
14021   return true;
14022 }
14023 
checkTasksDependOnProject()14024 bool QgisApp::checkTasksDependOnProject()
14025 {
14026   QSet< QString > activeTaskDescriptions;
14027   QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
14028   QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
14029 
14030   for ( ; layerIt != layers.constEnd(); ++layerIt )
14031   {
14032     QList< QgsTask * > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layerIt.value() );
14033     if ( !tasks.isEmpty() )
14034     {
14035       const auto constTasks = tasks;
14036       for ( QgsTask *task : constTasks )
14037       {
14038         activeTaskDescriptions.insert( tr( " • %1" ).arg( task->description() ) );
14039       }
14040     }
14041   }
14042 
14043   if ( !activeTaskDescriptions.isEmpty() )
14044   {
14045     QMessageBox::warning( this, tr( "Active Tasks" ),
14046                           tr( "The following tasks are currently running which depend on layers in this project:\n\n%1\n\nPlease cancel these tasks and retry." ).arg( qgis::setToList( activeTaskDescriptions ).join( QLatin1Char( '\n' ) ) ) );
14047     return true;
14048   }
14049   return false;
14050 }
14051 
closeProject()14052 void QgisApp::closeProject()
14053 {
14054   QgsCanvasRefreshBlocker refreshBlocker;
14055 
14056   // unload the project macros before changing anything
14057   if ( mPythonMacrosEnabled )
14058   {
14059     QgsPythonRunner::run( QStringLiteral( "qgis.utils.unloadProjectMacros();" ) );
14060   }
14061 
14062   mPythonMacrosEnabled = false;
14063 
14064   mLegendExpressionFilterButton->setExpressionText( QString() );
14065   mLegendExpressionFilterButton->setChecked( false );
14066   mFilterLegendByMapContentAction->setChecked( false );
14067 
14068   closeAdditionalMapCanvases();
14069   closeAdditional3DMapCanvases();
14070 
14071   deleteLayoutDesigners();
14072 
14073   // ensure layout widgets are fully deleted
14074   QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
14075 
14076   removeAnnotationItems();
14077 
14078   // clear out any stuff from project
14079   mMapCanvas->setLayers( QList<QgsMapLayer *>() );
14080   mMapCanvas->clearCache();
14081   mMapCanvas->cancelJobs();
14082   mOverviewCanvas->setLayers( QList<QgsMapLayer *>() );
14083 
14084   // Avoid unnecessary layer changed handling for each layer removed - instead,
14085   // defer the handling until we've removed all layers
14086   mBlockActiveLayerChanged = true;
14087   QgsProject::instance()->clear();
14088   mBlockActiveLayerChanged = false;
14089 
14090   onActiveLayerChanged( activeLayer() );
14091 }
14092 
14093 
changeEvent(QEvent * event)14094 void QgisApp::changeEvent( QEvent *event )
14095 {
14096   QMainWindow::changeEvent( event );
14097 #ifdef Q_OS_MAC
14098   switch ( event->type() )
14099   {
14100     case QEvent::ActivationChange:
14101       if ( QApplication::activeWindow() == this )
14102       {
14103         mWindowAction->setChecked( true );
14104       }
14105       // this should not be necessary since the action is part of an action group
14106       // however this check is not cleared if PrintComposer is closed and reopened
14107       else
14108       {
14109         mWindowAction->setChecked( false );
14110       }
14111       break;
14112 
14113     case QEvent::WindowTitleChange:
14114       mWindowAction->setText( windowTitle() );
14115       break;
14116 
14117     default:
14118       break;
14119   }
14120 #endif
14121 }
14122 
closeEvent(QCloseEvent * event)14123 void QgisApp::closeEvent( QCloseEvent *event )
14124 {
14125   // We'll close in our own good time, thank you very much
14126   event->ignore();
14127   // Do the usual checks and ask if they want to save, etc
14128   fileExit();
14129 }
14130 
getPluginMenu(const QString & menuName)14131 QMenu *QgisApp::getPluginMenu( const QString &menuName )
14132 {
14133   /* Plugin menu items are below the plugin separator (which may not exist yet
14134    * if no plugins are loaded) and above the python separator. If python is not
14135    * present, there is no python separator and the plugin list is at the bottom
14136    * of the menu.
14137    */
14138 
14139   QString cleanedMenuName = menuName;
14140 #ifdef Q_OS_MAC
14141   // Mac doesn't have '&' keyboard shortcuts.
14142   cleanedMenuName.remove( QChar( '&' ) );
14143 #endif
14144   QAction *before = mActionPluginSeparator2;  // python separator or end of list
14145   if ( !mActionPluginSeparator1 )
14146   {
14147     // First plugin - create plugin list separator
14148     mActionPluginSeparator1 = mPluginMenu->insertSeparator( before );
14149   }
14150   else
14151   {
14152     QString dst = cleanedMenuName;
14153     dst.remove( QChar( '&' ) );
14154 
14155     // Plugins exist - search between plugin separator and python separator or end of list
14156     QList<QAction *> actions = mPluginMenu->actions();
14157     int end = mActionPluginSeparator2 ? actions.indexOf( mActionPluginSeparator2 ) : actions.count();
14158     for ( int i = actions.indexOf( mActionPluginSeparator1 ) + 1; i < end; i++ )
14159     {
14160       QString src = actions.at( i )->text();
14161       src.remove( QChar( '&' ) );
14162 
14163       int comp = dst.localeAwareCompare( src );
14164       if ( comp < 0 )
14165       {
14166         // Add item before this one
14167         before = actions.at( i );
14168         break;
14169       }
14170       else if ( comp == 0 )
14171       {
14172         // Plugin menu item already exists
14173         return actions.at( i )->menu();
14174       }
14175     }
14176   }
14177   // It doesn't exist, so create
14178   QMenu *menu = new QMenu( cleanedMenuName, this );
14179   menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
14180   // Where to put it? - we worked that out above...
14181   mPluginMenu->insertMenu( before, menu );
14182 
14183   return menu;
14184 }
14185 
addPluginToMenu(const QString & name,QAction * action)14186 void QgisApp::addPluginToMenu( const QString &name, QAction *action )
14187 {
14188   QMenu *menu = getPluginMenu( name );
14189   menu->addAction( action );
14190 }
14191 
removePluginMenu(const QString & name,QAction * action)14192 void QgisApp::removePluginMenu( const QString &name, QAction *action )
14193 {
14194   QMenu *menu = getPluginMenu( name );
14195   menu->removeAction( action );
14196   if ( menu->actions().isEmpty() )
14197   {
14198     mPluginMenu->removeAction( menu->menuAction() );
14199   }
14200   // Remove separator above plugins in Plugin menu if no plugins remain
14201   QList<QAction *> actions = mPluginMenu->actions();
14202   int end = mActionPluginSeparator2 ? actions.indexOf( mActionPluginSeparator2 ) : actions.count();
14203   if ( actions.indexOf( mActionPluginSeparator1 ) + 1 == end )
14204   {
14205     mPluginMenu->removeAction( mActionPluginSeparator1 );
14206     mActionPluginSeparator1 = nullptr;
14207   }
14208 }
14209 
getDatabaseMenu(const QString & menuName)14210 QMenu *QgisApp::getDatabaseMenu( const QString &menuName )
14211 {
14212   if ( menuName.isEmpty() )
14213     return mDatabaseMenu;
14214 
14215   QString cleanedMenuName = menuName;
14216 #ifdef Q_OS_MAC
14217   // Mac doesn't have '&' keyboard shortcuts.
14218   cleanedMenuName.remove( QChar( '&' ) );
14219 #endif
14220   QString dst = cleanedMenuName;
14221   dst.remove( QChar( '&' ) );
14222 
14223   QAction *before = nullptr;
14224   QList<QAction *> actions = mDatabaseMenu->actions();
14225   for ( int i = 0; i < actions.count(); i++ )
14226   {
14227     QString src = actions.at( i )->text();
14228     src.remove( QChar( '&' ) );
14229 
14230     int comp = dst.localeAwareCompare( src );
14231     if ( comp < 0 )
14232     {
14233       // Add item before this one
14234       before = actions.at( i );
14235       break;
14236     }
14237     else if ( comp == 0 )
14238     {
14239       // Plugin menu item already exists
14240       return actions.at( i )->menu();
14241     }
14242   }
14243   // It doesn't exist, so create
14244   QMenu *menu = new QMenu( cleanedMenuName, this );
14245   menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
14246   if ( before )
14247     mDatabaseMenu->insertMenu( before, menu );
14248   else
14249     mDatabaseMenu->addMenu( menu );
14250 
14251   return menu;
14252 }
14253 
getRasterMenu(const QString & menuName)14254 QMenu *QgisApp::getRasterMenu( const QString &menuName )
14255 {
14256   if ( menuName.isEmpty() )
14257     return mRasterMenu;
14258 
14259   QString cleanedMenuName = menuName;
14260 #ifdef Q_OS_MAC
14261   // Mac doesn't have '&' keyboard shortcuts.
14262   cleanedMenuName.remove( QChar( '&' ) );
14263 #endif
14264 
14265   QAction *before = nullptr;
14266   if ( !mActionRasterSeparator )
14267   {
14268     // First plugin - create plugin list separator
14269     mActionRasterSeparator = mRasterMenu->insertSeparator( before );
14270   }
14271   else
14272   {
14273     QString dst = cleanedMenuName;
14274     dst.remove( QChar( '&' ) );
14275     // Plugins exist - search between plugin separator and python separator or end of list
14276     QList<QAction *> actions = mRasterMenu->actions();
14277     for ( int i = actions.indexOf( mActionRasterSeparator ) + 1; i < actions.count(); i++ )
14278     {
14279       QString src = actions.at( i )->text();
14280       src.remove( QChar( '&' ) );
14281 
14282       int comp = dst.localeAwareCompare( src );
14283       if ( comp < 0 )
14284       {
14285         // Add item before this one
14286         before = actions.at( i );
14287         break;
14288       }
14289       else if ( comp == 0 )
14290       {
14291         // Plugin menu item already exists
14292         return actions.at( i )->menu();
14293       }
14294     }
14295   }
14296 
14297   // It doesn't exist, so create
14298   QMenu *menu = new QMenu( cleanedMenuName, this );
14299   menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
14300   if ( before )
14301     mRasterMenu->insertMenu( before, menu );
14302   else
14303     mRasterMenu->addMenu( menu );
14304 
14305   return menu;
14306 }
14307 
getVectorMenu(const QString & menuName)14308 QMenu *QgisApp::getVectorMenu( const QString &menuName )
14309 {
14310   if ( menuName.isEmpty() )
14311     return mVectorMenu;
14312 
14313   QString cleanedMenuName = menuName;
14314 #ifdef Q_OS_MAC
14315   // Mac doesn't have '&' keyboard shortcuts.
14316   cleanedMenuName.remove( QChar( '&' ) );
14317 #endif
14318   QString dst = cleanedMenuName;
14319   dst.remove( QChar( '&' ) );
14320 
14321   QAction *before = nullptr;
14322   QList<QAction *> actions = mVectorMenu->actions();
14323   for ( int i = 0; i < actions.count(); i++ )
14324   {
14325     QString src = actions.at( i )->text();
14326     src.remove( QChar( '&' ) );
14327 
14328     int comp = dst.localeAwareCompare( src );
14329     if ( comp < 0 )
14330     {
14331       // Add item before this one
14332       before = actions.at( i );
14333       break;
14334     }
14335     else if ( comp == 0 )
14336     {
14337       // Plugin menu item already exists
14338       return actions.at( i )->menu();
14339     }
14340   }
14341   // It doesn't exist, so create
14342   QMenu *menu = new QMenu( cleanedMenuName, this );
14343   menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
14344   if ( before )
14345     mVectorMenu->insertMenu( before, menu );
14346   else
14347     mVectorMenu->addMenu( menu );
14348 
14349   return menu;
14350 }
14351 
getWebMenu(const QString & menuName)14352 QMenu *QgisApp::getWebMenu( const QString &menuName )
14353 {
14354   if ( menuName.isEmpty() )
14355     return mWebMenu;
14356 
14357   QString cleanedMenuName = menuName;
14358 #ifdef Q_OS_MAC
14359   // Mac doesn't have '&' keyboard shortcuts.
14360   cleanedMenuName.remove( QChar( '&' ) );
14361 #endif
14362   QString dst = cleanedMenuName;
14363   dst.remove( QChar( '&' ) );
14364 
14365   QAction *before = nullptr;
14366   QList<QAction *> actions = mWebMenu->actions();
14367   for ( int i = 0; i < actions.count(); i++ )
14368   {
14369     QString src = actions.at( i )->text();
14370     src.remove( QChar( '&' ) );
14371 
14372     int comp = dst.localeAwareCompare( src );
14373     if ( comp < 0 )
14374     {
14375       // Add item before this one
14376       before = actions.at( i );
14377       break;
14378     }
14379     else if ( comp == 0 )
14380     {
14381       // Plugin menu item already exists
14382       return actions.at( i )->menu();
14383     }
14384   }
14385   // It doesn't exist, so create
14386   QMenu *menu = new QMenu( cleanedMenuName, this );
14387   menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
14388   if ( before )
14389     mWebMenu->insertMenu( before, menu );
14390   else
14391     mWebMenu->addMenu( menu );
14392 
14393   return menu;
14394 }
14395 
insertAddLayerAction(QAction * action)14396 void QgisApp::insertAddLayerAction( QAction *action )
14397 {
14398   mAddLayerMenu->insertAction( mActionAddLayerSeparator, action );
14399 }
14400 
removeAddLayerAction(QAction * action)14401 void QgisApp::removeAddLayerAction( QAction *action )
14402 {
14403   mAddLayerMenu->removeAction( action );
14404 }
14405 
addPluginToDatabaseMenu(const QString & name,QAction * action)14406 void QgisApp::addPluginToDatabaseMenu( const QString &name, QAction *action )
14407 {
14408   QMenu *menu = getDatabaseMenu( name );
14409   menu->addAction( action );
14410 
14411   // add the Database menu to the menuBar if not added yet
14412   if ( mDatabaseMenu->actions().count() != 1 )
14413     return;
14414 
14415   QAction *before = nullptr;
14416   QList<QAction *> actions = menuBar()->actions();
14417   for ( int i = 0; i < actions.count(); i++ )
14418   {
14419     if ( actions.at( i )->menu() == mDatabaseMenu )
14420       return;
14421 
14422     // goes before Web menu, if present
14423     if ( actions.at( i )->menu() == mWebMenu )
14424     {
14425       before = actions.at( i );
14426       break;
14427     }
14428   }
14429   for ( int i = 0; i < actions.count(); i++ )
14430   {
14431     // defaults to after Raster menu, which is already in qgisapp.ui
14432     if ( actions.at( i )->menu() == mRasterMenu )
14433     {
14434       if ( !before )
14435       {
14436         before = actions.at( i += 1 );
14437         break;
14438       }
14439     }
14440   }
14441   if ( before )
14442     menuBar()->insertMenu( before, mDatabaseMenu );
14443   else
14444     // fallback insert
14445     menuBar()->insertMenu( firstRightStandardMenu()->menuAction(), mDatabaseMenu );
14446 }
14447 
addPluginToRasterMenu(const QString & name,QAction * action)14448 void QgisApp::addPluginToRasterMenu( const QString &name, QAction *action )
14449 {
14450   QMenu *menu = getRasterMenu( name );
14451   menu->addAction( action );
14452 }
14453 
addPluginToVectorMenu(const QString & name,QAction * action)14454 void QgisApp::addPluginToVectorMenu( const QString &name, QAction *action )
14455 {
14456   QMenu *menu = getVectorMenu( name );
14457   menu->addAction( action );
14458 }
14459 
addPluginToWebMenu(const QString & name,QAction * action)14460 void QgisApp::addPluginToWebMenu( const QString &name, QAction *action )
14461 {
14462   QMenu *menu = getWebMenu( name );
14463   menu->addAction( action );
14464 
14465   // add the Vector menu to the menuBar if not added yet
14466   if ( mWebMenu->actions().count() != 1 )
14467     return;
14468 
14469   QAction *before = nullptr;
14470   QList<QAction *> actions = menuBar()->actions();
14471   for ( int i = 0; i < actions.count(); i++ )
14472   {
14473     // goes after Database menu, if present
14474     if ( actions.at( i )->menu() == mDatabaseMenu )
14475     {
14476       before = actions.at( i += 1 );
14477       // don't break here
14478     }
14479 
14480     if ( actions.at( i )->menu() == mWebMenu )
14481       return;
14482   }
14483   for ( int i = 0; i < actions.count(); i++ )
14484   {
14485     // defaults to after Raster menu, which is already in qgisapp.ui
14486     if ( actions.at( i )->menu() == mRasterMenu )
14487     {
14488       if ( !before )
14489       {
14490         before = actions.at( i += 1 );
14491         break;
14492       }
14493     }
14494   }
14495 
14496   if ( before )
14497     menuBar()->insertMenu( before, mWebMenu );
14498   else
14499     // fallback insert
14500     menuBar()->insertMenu( firstRightStandardMenu()->menuAction(), mWebMenu );
14501 }
14502 
removePluginDatabaseMenu(const QString & name,QAction * action)14503 void QgisApp::removePluginDatabaseMenu( const QString &name, QAction *action )
14504 {
14505   QMenu *menu = getDatabaseMenu( name );
14506   menu->removeAction( action );
14507   if ( menu->actions().isEmpty() )
14508   {
14509     mDatabaseMenu->removeAction( menu->menuAction() );
14510   }
14511 
14512   // remove the Database menu from the menuBar if there are no more actions
14513   if ( !mDatabaseMenu->actions().isEmpty() )
14514     return;
14515 
14516   QList<QAction *> actions = menuBar()->actions();
14517   for ( int i = 0; i < actions.count(); i++ )
14518   {
14519     if ( actions.at( i )->menu() == mDatabaseMenu )
14520     {
14521       menuBar()->removeAction( actions.at( i ) );
14522       return;
14523     }
14524   }
14525 }
14526 
removePluginRasterMenu(const QString & name,QAction * action)14527 void QgisApp::removePluginRasterMenu( const QString &name, QAction *action )
14528 {
14529   QMenu *menu = getRasterMenu( name );
14530   menu->removeAction( action );
14531   if ( menu->actions().isEmpty() )
14532   {
14533     mRasterMenu->removeAction( menu->menuAction() );
14534   }
14535 
14536   // Remove separator above plugins in Raster menu if no plugins remain
14537   QList<QAction *> actions = mRasterMenu->actions();
14538   if ( actions.indexOf( mActionRasterSeparator ) + 1 == actions.count() )
14539   {
14540     mRasterMenu->removeAction( mActionRasterSeparator );
14541     mActionRasterSeparator = nullptr;
14542   }
14543 }
14544 
removePluginVectorMenu(const QString & name,QAction * action)14545 void QgisApp::removePluginVectorMenu( const QString &name, QAction *action )
14546 {
14547   QMenu *menu = getVectorMenu( name );
14548   menu->removeAction( action );
14549   if ( menu->actions().isEmpty() )
14550   {
14551     mVectorMenu->removeAction( menu->menuAction() );
14552   }
14553 
14554   // remove the Vector menu from the menuBar if there are no more actions
14555   if ( !mVectorMenu->actions().isEmpty() )
14556     return;
14557 
14558   QList<QAction *> actions = menuBar()->actions();
14559   for ( int i = 0; i < actions.count(); i++ )
14560   {
14561     if ( actions.at( i )->menu() == mVectorMenu )
14562     {
14563       menuBar()->removeAction( actions.at( i ) );
14564       return;
14565     }
14566   }
14567 }
14568 
removePluginWebMenu(const QString & name,QAction * action)14569 void QgisApp::removePluginWebMenu( const QString &name, QAction *action )
14570 {
14571   QMenu *menu = getWebMenu( name );
14572   menu->removeAction( action );
14573   if ( menu->actions().isEmpty() )
14574   {
14575     mWebMenu->removeAction( menu->menuAction() );
14576   }
14577 
14578   // remove the Web menu from the menuBar if there are no more actions
14579   if ( !mWebMenu->actions().isEmpty() )
14580     return;
14581 
14582   QList<QAction *> actions = menuBar()->actions();
14583   for ( int i = 0; i < actions.count(); i++ )
14584   {
14585     if ( actions.at( i )->menu() == mWebMenu )
14586     {
14587       menuBar()->removeAction( actions.at( i ) );
14588       return;
14589     }
14590   }
14591 }
14592 
addPluginToolBarIcon(QAction * qAction)14593 int QgisApp::addPluginToolBarIcon( QAction *qAction )
14594 {
14595   mPluginToolBar->addAction( qAction );
14596   return 0;
14597 }
14598 
addPluginToolBarWidget(QWidget * widget)14599 QAction *QgisApp::addPluginToolBarWidget( QWidget *widget )
14600 {
14601   return mPluginToolBar->addWidget( widget );
14602 }
14603 
removePluginToolBarIcon(QAction * qAction)14604 void QgisApp::removePluginToolBarIcon( QAction *qAction )
14605 {
14606   mPluginToolBar->removeAction( qAction );
14607 }
14608 
addRasterToolBarIcon(QAction * qAction)14609 int QgisApp::addRasterToolBarIcon( QAction *qAction )
14610 {
14611   mRasterToolBar->addAction( qAction );
14612   return 0;
14613 }
14614 
addRasterToolBarWidget(QWidget * widget)14615 QAction *QgisApp::addRasterToolBarWidget( QWidget *widget )
14616 {
14617   return mRasterToolBar->addWidget( widget );
14618 }
14619 
removeRasterToolBarIcon(QAction * qAction)14620 void QgisApp::removeRasterToolBarIcon( QAction *qAction )
14621 {
14622   mRasterToolBar->removeAction( qAction );
14623 }
14624 
addVectorToolBarIcon(QAction * qAction)14625 int QgisApp::addVectorToolBarIcon( QAction *qAction )
14626 {
14627   mVectorToolBar->addAction( qAction );
14628   return 0;
14629 }
14630 
addVectorToolBarWidget(QWidget * widget)14631 QAction *QgisApp::addVectorToolBarWidget( QWidget *widget )
14632 {
14633   return mVectorToolBar->addWidget( widget );
14634 }
14635 
removeVectorToolBarIcon(QAction * qAction)14636 void QgisApp::removeVectorToolBarIcon( QAction *qAction )
14637 {
14638   mVectorToolBar->removeAction( qAction );
14639 }
14640 
addDatabaseToolBarIcon(QAction * qAction)14641 int QgisApp::addDatabaseToolBarIcon( QAction *qAction )
14642 {
14643   mDatabaseToolBar->addAction( qAction );
14644   return 0;
14645 }
14646 
onVirtualLayerAdded(const QString & uri,const QString & layerName)14647 void QgisApp::onVirtualLayerAdded( const QString &uri, const QString &layerName )
14648 {
14649   addVectorLayer( uri, layerName, QStringLiteral( "virtual" ) );
14650 }
14651 
addDatabaseToolBarWidget(QWidget * widget)14652 QAction *QgisApp::addDatabaseToolBarWidget( QWidget *widget )
14653 {
14654   return mDatabaseToolBar->addWidget( widget );
14655 }
14656 
removeDatabaseToolBarIcon(QAction * qAction)14657 void QgisApp::removeDatabaseToolBarIcon( QAction *qAction )
14658 {
14659   mDatabaseToolBar->removeAction( qAction );
14660 }
14661 
addWebToolBarIcon(QAction * qAction)14662 int QgisApp::addWebToolBarIcon( QAction *qAction )
14663 {
14664   mWebToolBar->addAction( qAction );
14665   return 0;
14666 }
14667 
addWebToolBarWidget(QWidget * widget)14668 QAction *QgisApp::addWebToolBarWidget( QWidget *widget )
14669 {
14670   return mWebToolBar->addWidget( widget );
14671 }
14672 
removeWebToolBarIcon(QAction * qAction)14673 void QgisApp::removeWebToolBarIcon( QAction *qAction )
14674 {
14675   mWebToolBar->removeAction( qAction );
14676 }
14677 
updateCrsStatusBar()14678 void QgisApp::updateCrsStatusBar()
14679 {
14680   const QgsCoordinateReferenceSystem projectCrs = QgsProject::instance()->crs();
14681   if ( projectCrs.isValid() )
14682   {
14683     if ( !projectCrs.authid().isEmpty() )
14684       mOnTheFlyProjectionStatusButton->setText( projectCrs.authid() );
14685     else
14686       mOnTheFlyProjectionStatusButton->setText( QObject::tr( "Unknown CRS" ) );
14687 
14688     mOnTheFlyProjectionStatusButton->setToolTip(
14689       tr( "Current CRS: %1" ).arg( projectCrs.userFriendlyIdentifier() ) );
14690     mOnTheFlyProjectionStatusButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconProjectionEnabled.svg" ) ) );
14691   }
14692   else
14693   {
14694     mOnTheFlyProjectionStatusButton->setText( QString() );
14695     mOnTheFlyProjectionStatusButton->setToolTip( tr( "No projection" ) );
14696     mOnTheFlyProjectionStatusButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconProjectionDisabled.svg" ) ) );
14697   }
14698 }
14699 
14700 // slot to update the progress bar in the status bar
showProgress(int progress,int totalSteps)14701 void QgisApp::showProgress( int progress, int totalSteps )
14702 {
14703   if ( progress == totalSteps )
14704   {
14705     mProgressBar->reset();
14706     mProgressBar->hide();
14707   }
14708   else
14709   {
14710     //only call show if not already hidden to reduce flicker
14711     if ( !mProgressBar->isVisible() )
14712     {
14713       mProgressBar->show();
14714     }
14715     mProgressBar->setMaximum( totalSteps );
14716     mProgressBar->setValue( progress );
14717   }
14718 }
14719 
mapToolChanged(QgsMapTool * newTool,QgsMapTool * oldTool)14720 void QgisApp::mapToolChanged( QgsMapTool *newTool, QgsMapTool *oldTool )
14721 {
14722   if ( oldTool )
14723   {
14724     disconnect( oldTool, &QgsMapTool::messageEmitted, this, &QgisApp::displayMapToolMessage );
14725     disconnect( oldTool, &QgsMapTool::messageEmitted, this, &QgisApp::displayMapToolMessage );
14726     disconnect( oldTool, &QgsMapTool::messageDiscarded, this, &QgisApp::removeMapToolMessage );
14727   }
14728 
14729   if ( newTool )
14730   {
14731     if ( !( newTool->flags() & QgsMapTool::EditTool ) )
14732     {
14733       mNonEditMapTool = newTool;
14734     }
14735 
14736     connect( newTool, &QgsMapTool::messageEmitted, this, &QgisApp::displayMapToolMessage );
14737     connect( newTool, &QgsMapTool::messageEmitted, this, &QgisApp::displayMapToolMessage );
14738     connect( newTool, &QgsMapTool::messageDiscarded, this, &QgisApp::removeMapToolMessage );
14739   }
14740 }
14741 
showMapCanvas()14742 void QgisApp::showMapCanvas()
14743 {
14744   // Map layers changed -> switch to map canvas
14745   if ( mCentralContainer )
14746     mCentralContainer->setCurrentIndex( 0 );
14747 }
14748 
markDirty()14749 void QgisApp::markDirty()
14750 {
14751   // notify the project that there was a change
14752   QgsProject::instance()->setDirty( true );
14753 }
14754 
extentChanged()14755 void QgisApp::extentChanged()
14756 {
14757   // allow symbols in the legend update their preview if they use map units
14758   mLayerTreeView->layerTreeModel()->setLegendMapViewData( mMapCanvas->mapUnitsPerPixel(),
14759       static_cast< int >( std::round( mMapCanvas->mapSettings().outputDpi() ) ), mMapCanvas->scale() );
14760 }
14761 
layersWereAdded(const QList<QgsMapLayer * > & layers)14762 void QgisApp::layersWereAdded( const QList<QgsMapLayer *> &layers )
14763 {
14764   const auto constLayers = layers;
14765   for ( QgsMapLayer *layer : constLayers )
14766   {
14767     connect( layer, &QgsMapLayer::layerModified, this, &QgisApp::updateLayerModifiedActions );
14768     connect( layer, &QgsMapLayer::editingStarted, this, &QgisApp::layerEditStateChanged );
14769     connect( layer, &QgsMapLayer::editingStopped, this, &QgisApp::layerEditStateChanged );
14770 
14771     if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
14772     {
14773       // notify user about any font family substitution, but only when rendering labels (i.e. not when opening settings dialog)
14774       connect( vlayer, &QgsVectorLayer::labelingFontNotFound, this, &QgisApp::labelingFontNotFound );
14775 
14776       // Do not check for layer editing capabilities because they may change
14777       // (for example when subsetString is added/removed) and signals need to
14778       // be in place in order to update the GUI
14779       connect( vlayer, &QgsVectorLayer::readOnlyChanged, this, &QgisApp::layerEditStateChanged );
14780       connect( vlayer, &QgsVectorLayer::raiseError, this, &QgisApp::onLayerError );
14781       connect( vlayer, &QgsVectorLayer::styleLoaded, [this, vlayer]( QgsMapLayer::StyleCategories categories ) { vectorLayerStyleLoaded( vlayer, categories ); } );
14782     }
14783 
14784     if ( QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layer ) )
14785     {
14786       // connect up any request the raster may make to update the statusbar message
14787       connect( rlayer, &QgsRasterLayer::statusChanged, this, &QgisApp::showStatusMessage );
14788     }
14789 
14790     if ( QgsDataProvider *provider = layer->dataProvider() )
14791     {
14792       connect( provider, &QgsDataProvider::dataChanged, layer, [layer] { layer->triggerRepaint(); } );
14793       connect( provider, &QgsDataProvider::dataChanged, this, [this] { refreshMapCanvas(); } );
14794     }
14795   }
14796 }
14797 
showRotation()14798 void QgisApp::showRotation()
14799 {
14800   // update the statusbar with the current rotation.
14801   double myrotation = mMapCanvas->rotation();
14802   whileBlocking( mRotationEdit )->setValue( myrotation );
14803 }
14804 
showPanMessage(double distance,QgsUnitTypes::DistanceUnit unit,double bearing)14805 void QgisApp::showPanMessage( double distance, QgsUnitTypes::DistanceUnit unit, double bearing )
14806 {
14807   const bool showMessage = QgsSettings().value( QStringLiteral( "showPanDistanceInStatusBar" ), true, QgsSettings::App ).toBool();
14808   if ( !showMessage )
14809     return;
14810 
14811   const double distanceInProjectUnits = distance * QgsUnitTypes::fromUnitToUnitFactor( unit, QgsProject::instance()->distanceUnits() );
14812   const int distanceDecimalPlaces = QgsSettings().value( QStringLiteral( "qgis/measure/decimalplaces" ), 3 ).toInt();
14813   const QString distanceString = QgsDistanceArea::formatDistance( distanceInProjectUnits, distanceDecimalPlaces, QgsProject::instance()->distanceUnits() );
14814   const QString bearingString = mBearingNumericFormat->formatDouble( bearing, QgsNumericFormatContext() );
14815   mStatusBar->showMessage( tr( "Pan distance %1 (%2)" ).arg( distanceString, bearingString ), 2000 );
14816 }
14817 
selectionModeChanged(QgsMapToolSelect::Mode mode)14818 void QgisApp::selectionModeChanged( QgsMapToolSelect::Mode mode )
14819 {
14820   switch ( mode )
14821   {
14822     case QgsMapToolSelect::GeometryIntersectsSetSelection:
14823       mStatusBar->showMessage( QString() );
14824       break;
14825     case QgsMapToolSelect::GeometryIntersectsAddToSelection:
14826       mStatusBar->showMessage( tr( "Add to the current selection" ) );
14827       break;
14828 
14829     case QgsMapToolSelect::GeometryIntersectsSubtractFromSelection:
14830       mStatusBar->showMessage( tr( "Subtract from the current selection" ) );
14831       break;
14832 
14833     case QgsMapToolSelect::GeometryIntersectsIntersectWithSelection:
14834       mStatusBar->showMessage( tr( "Intersect with the current selection" ) );
14835       break;
14836 
14837     case QgsMapToolSelect::GeometryWithinSetSelection:
14838       mStatusBar->showMessage( tr( "Select features completely within" ) );
14839       break;
14840 
14841     case QgsMapToolSelect::GeometryWithinAddToSelection:
14842       mStatusBar->showMessage( tr( "Add features completely within to the current selection" ) );
14843       break;
14844 
14845     case QgsMapToolSelect::GeometryWithinSubtractFromSelection:
14846       mStatusBar->showMessage( tr( "Subtract features completely within from the current selection" ) );
14847       break;
14848 
14849     case QgsMapToolSelect::GeometryWithinIntersectWithSelection:
14850       mStatusBar->showMessage( tr( "Intersect features completely within with the current selection" ) );
14851       break;
14852 
14853   }
14854 }
14855 
updateMouseCoordinatePrecision()14856 void QgisApp::updateMouseCoordinatePrecision()
14857 {
14858   mCoordsEdit->setMouseCoordinatesPrecision( QgsCoordinateUtils::calculateCoordinatePrecision( mapCanvas()->mapUnitsPerPixel(), mapCanvas()->mapSettings().destinationCrs() ) );
14859 }
14860 
showStatusMessage(const QString & message)14861 void QgisApp::showStatusMessage( const QString &message )
14862 {
14863   mStatusBar->showMessage( message );
14864 }
14865 
loadingLayerMessages(const QString & layerName,const QList<QgsReadWriteContext::ReadWriteMessage> & messages)14866 void QgisApp::loadingLayerMessages( const QString &layerName, const QList<QgsReadWriteContext::ReadWriteMessage> &messages )
14867 {
14868   QVector<QgsReadWriteContext::ReadWriteMessage> shownMessages;
14869   for ( const QgsReadWriteContext::ReadWriteMessage &message : messages )
14870   {
14871     if ( shownMessages.contains( message ) )
14872       continue;
14873 
14874     visibleMessageBar()->pushMessage( layerName, message.message(), message.categories().join( '\n' ), message.level() );
14875 
14876     shownMessages.append( message );
14877   }
14878 }
14879 
displayMapToolMessage(const QString & message,Qgis::MessageLevel level)14880 void QgisApp::displayMapToolMessage( const QString &message, Qgis::MessageLevel level )
14881 {
14882   // remove previous message
14883   messageBar()->popWidget( mLastMapToolMessage );
14884 
14885   QgsMapTool *tool = mapCanvas()->mapTool();
14886 
14887   if ( tool )
14888   {
14889     mLastMapToolMessage = new QgsMessageBarItem( tool->toolName(), message, level );
14890     messageBar()->pushItem( mLastMapToolMessage );
14891   }
14892 }
14893 
displayMessage(const QString & title,const QString & message,Qgis::MessageLevel level)14894 void QgisApp::displayMessage( const QString &title, const QString &message, Qgis::MessageLevel level )
14895 {
14896   visibleMessageBar()->pushMessage( title, message, level );
14897 }
14898 
removeMapToolMessage()14899 void QgisApp::removeMapToolMessage()
14900 {
14901   // remove previous message
14902   messageBar()->popWidget( mLastMapToolMessage );
14903 }
14904 
14905 
14906 // Show the maptip using tooltip
showMapTip()14907 void QgisApp::showMapTip()
14908 {
14909   // Only show maptips if the mouse is still over the map canvas when timer is triggered
14910   if ( mMapCanvas->underMouse() )
14911   {
14912     QPoint myPointerPos = mMapCanvas->mouseLastXY();
14913 
14914     //  Make sure there is an active layer before proceeding
14915     QgsMapLayer *mypLayer = mMapCanvas->currentLayer();
14916     if ( mypLayer )
14917     {
14918       // only process vector layers
14919       if ( mypLayer->type() == QgsMapLayerType::VectorLayer )
14920       {
14921         // Show the maptip if the maptips button is depressed
14922         if ( mMapTipsVisible )
14923         {
14924           mpMaptip->showMapTip( mypLayer, mLastMapPosition, myPointerPos, mMapCanvas );
14925         }
14926       }
14927     }
14928   }
14929 }
14930 
projectPropertiesProjections()14931 void QgisApp::projectPropertiesProjections()
14932 {
14933   // display the project props dialog and switch to the projections tab
14934   projectProperties( QStringLiteral( "mProjOptsCRS" ) );
14935 }
14936 
projectProperties(const QString & currentPage)14937 void QgisApp::projectProperties( const QString &currentPage )
14938 {
14939   QList< QgsOptionsWidgetFactory * > factories;
14940   const auto constProjectPropertiesWidgetFactories = mProjectPropertiesWidgetFactories;
14941   for ( const QPointer< QgsOptionsWidgetFactory > &f : constProjectPropertiesWidgetFactories )
14942   {
14943     if ( f )
14944       factories << f;
14945   }
14946   QgsProjectProperties pp( mMapCanvas, this, QgsGuiUtils::ModalDialogFlags, factories );
14947 
14948   qApp->processEvents();
14949 
14950   // Be told if the mouse display precision may have changed by the user
14951   // changing things in the project properties dialog box
14952   connect( &pp, &QgsProjectProperties::displayPrecisionChanged, this,
14953            &QgisApp::updateMouseCoordinatePrecision );
14954 
14955   if ( !currentPage.isEmpty() )
14956   {
14957     pp.setCurrentPage( currentPage );
14958   }
14959   // Display the modal dialog box.
14960   pp.exec();
14961 
14962   mMapTools->mapTool< QgsMeasureTool >( QgsAppMapTools::MeasureDistance )->updateSettings();
14963   mMapTools->mapTool< QgsMeasureTool >( QgsAppMapTools::MeasureArea )->updateSettings();
14964   mMapTools->mapTool< QgsMapToolMeasureAngle >( QgsAppMapTools::MeasureAngle )->updateSettings();
14965   mMapTools->mapTool< QgsMapToolMeasureBearing >( QgsAppMapTools::MeasureBearing )->updateSettings();
14966 
14967   // Set the window title.
14968   setTitleBarText_( *this );
14969 }
14970 
14971 
clipboard()14972 QgsClipboard *QgisApp::clipboard()
14973 {
14974   return mInternalClipboard;
14975 }
14976 
selectionChanged(QgsMapLayer * layer)14977 void QgisApp::selectionChanged( QgsMapLayer *layer )
14978 {
14979   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
14980   if ( vlayer )
14981   {
14982     const int selectedCount = vlayer->selectedFeatureCount();
14983     if ( selectedCount == 1 )
14984     {
14985       QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( vlayer ) );
14986       QgsExpression exp = vlayer->displayExpression();
14987       exp.prepare( &context );
14988 
14989       QgsFeatureRequest request = QgsFeatureRequest().setSubsetOfAttributes( exp.referencedColumns(), vlayer->fields() );
14990       if ( !exp.needsGeometry() )
14991         request.setFlags( request.flags() | QgsFeatureRequest::NoGeometry );
14992 
14993       QgsFeature feat;
14994       QgsFeatureIterator featureIt = vlayer->getSelectedFeatures( request );
14995       while ( featureIt.nextFeature( feat ) )
14996       {
14997         context.setFeature( feat );
14998         QString featureTitle = exp.evaluate( &context ).toString();
14999         showStatusMessage( tr( "1 feature selected on layer %1 (%2)." ).arg( vlayer->name(), featureTitle ) );
15000         break;
15001       }
15002     }
15003     else
15004     {
15005       showStatusMessage( tr( "%n features selected on layer %1.", "number of selected features", selectedCount ).arg( vlayer->name() ) );
15006     }
15007   }
15008   if ( layer == activeLayer() )
15009   {
15010     activateDeactivateLayerRelatedActions( layer );
15011   }
15012 
15013   activateDeactivateMultipleLayersRelatedActions();
15014 }
15015 
legendLayerSelectionChanged()15016 void QgisApp::legendLayerSelectionChanged()
15017 {
15018   const QList<QgsLayerTreeLayer *> selectedLayers = mLayerTreeView ? mLayerTreeView->selectedLayerNodes() : QList<QgsLayerTreeLayer *>();
15019 
15020   mActionDuplicateLayer->setEnabled( !selectedLayers.isEmpty() );
15021   mActionSetLayerScaleVisibility->setEnabled( !selectedLayers.isEmpty() );
15022   mActionSetLayerCRS->setEnabled( !selectedLayers.isEmpty() );
15023   mActionSetProjectCRSFromLayer->setEnabled( selectedLayers.count() == 1 );
15024 
15025   mActionSaveEdits->setEnabled( QgsLayerTreeUtils::layersModified( selectedLayers ) );
15026   mActionRollbackEdits->setEnabled( QgsLayerTreeUtils::layersModified( selectedLayers ) );
15027   mActionCancelEdits->setEnabled( QgsLayerTreeUtils::layersEditable( selectedLayers ) );
15028 
15029   mLegendExpressionFilterButton->setEnabled( false );
15030   mLegendExpressionFilterButton->setVectorLayer( nullptr );
15031   if ( selectedLayers.size() == 1 )
15032   {
15033     QgsLayerTreeLayer *l = selectedLayers.front();
15034     if ( l->layer() && l->layer()->type() == QgsMapLayerType::VectorLayer )
15035     {
15036       mLegendExpressionFilterButton->setEnabled( true );
15037       bool exprEnabled;
15038       QString expr = QgsLayerTreeUtils::legendFilterByExpression( *l, &exprEnabled );
15039       mLegendExpressionFilterButton->setExpressionText( expr );
15040       mLegendExpressionFilterButton->setVectorLayer( qobject_cast<QgsVectorLayer *>( l->layer() ) );
15041       mLegendExpressionFilterButton->setChecked( exprEnabled );
15042     }
15043   }
15044 
15045   // remove action - check for required layers
15046   bool removeEnabled = true;
15047   for ( QgsLayerTreeLayer *nodeLayer : selectedLayers )
15048   {
15049     if ( nodeLayer->layer() && !nodeLayer->layer()->flags().testFlag( QgsMapLayer::Removable ) )
15050     {
15051       removeEnabled = false;
15052       break;
15053     }
15054   }
15055   mActionRemoveLayer->setEnabled( removeEnabled );
15056 }
15057 
layerEditStateChanged()15058 void QgisApp::layerEditStateChanged()
15059 {
15060   QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
15061   if ( layer && layer == activeLayer() )
15062   {
15063     activateDeactivateLayerRelatedActions( layer );
15064     mSaveRollbackInProgress = false;
15065   }
15066 }
15067 
updateLabelToolButtons()15068 void QgisApp::updateLabelToolButtons()
15069 {
15070   bool enableMove = false, enableRotate = false, enablePin = false, enableShowHide = false, enableChange = false;
15071 
15072   QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
15073   for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
15074   {
15075     QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( it.value() );
15076     if ( vlayer && ( vlayer->diagramsEnabled() || vlayer->labelsEnabled() ) )
15077     {
15078       enablePin = true;
15079       enableShowHide = true;
15080       enableMove = true;
15081       enableRotate = true;
15082       enableChange = true;
15083 
15084       break;
15085     }
15086   }
15087 
15088   mActionPinLabels->setEnabled( enablePin );
15089   mActionShowHideLabels->setEnabled( enableShowHide );
15090   mActionMoveLabel->setEnabled( enableMove );
15091   mActionRotateLabel->setEnabled( enableRotate );
15092   mActionChangeLabelProperties->setEnabled( enableChange );
15093 }
15094 
selectedLayersHaveSelection()15095 bool QgisApp::selectedLayersHaveSelection()
15096 {
15097   const QList<QgsMapLayer *> layers = mLayerTreeView->selectedLayers();
15098 
15099   // If no selected layers, use active layer
15100   if ( layers.empty() && activeLayer() )
15101   {
15102     if ( QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( activeLayer() ) )
15103       return layer->selectedFeatureCount() > 0;
15104   }
15105 
15106   for ( QgsMapLayer *mapLayer : layers )
15107   {
15108     QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer );
15109 
15110     if ( !layer || !layer->isSpatial() || layer->selectedFeatureCount() == 0 )
15111       continue;
15112 
15113     return true;
15114   }
15115 
15116   return false;
15117 }
15118 
selectedLayersHaveSpatial()15119 bool QgisApp::selectedLayersHaveSpatial()
15120 {
15121   const QList<QgsMapLayer *> layers = mLayerTreeView->selectedLayers();
15122 
15123   // If no selected layers, use active layer
15124   if ( layers.empty() && activeLayer() )
15125     return activeLayer()->isSpatial();
15126 
15127   for ( QgsMapLayer *mapLayer : layers )
15128   {
15129     if ( !mapLayer || !mapLayer->isSpatial() )
15130       continue;
15131 
15132     return true;
15133   }
15134 
15135   return false;
15136 }
15137 
activateDeactivateMultipleLayersRelatedActions()15138 void QgisApp::activateDeactivateMultipleLayersRelatedActions()
15139 {
15140   // these actions are enabled whenever ANY selected layer is spatial
15141   const bool hasSpatial = selectedLayersHaveSpatial();
15142   mActionZoomToLayers->setEnabled( hasSpatial );
15143 
15144   // this action is enabled whenever ANY selected layer has a selection
15145   const bool hasSelection = selectedLayersHaveSelection();
15146   mActionPanToSelected->setEnabled( hasSelection );
15147   mActionZoomToSelected->setEnabled( hasSelection );
15148 }
15149 
activateDeactivateLayerRelatedActions(QgsMapLayer * layer)15150 void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
15151 {
15152   updateLabelToolButtons();
15153 
15154   mMenuPasteAs->setEnabled( clipboard() && !clipboard()->isEmpty() );
15155   mActionPasteAsNewVector->setEnabled( clipboard() && !clipboard()->isEmpty() );
15156   mActionPasteAsNewMemoryVector->setEnabled( clipboard() && !clipboard()->isEmpty() );
15157 
15158   updateLayerModifiedActions();
15159 
15160   QgsAbstractMapToolHandler::Context context;
15161   for ( QgsAbstractMapToolHandler *handler : std::as_const( mMapToolHandlers ) )
15162   {
15163     handler->action()->setEnabled( handler->isCompatibleWithLayer( layer, context ) );
15164     if ( handler->mapTool() == mMapCanvas->mapTool() )
15165     {
15166       if ( !handler->action()->isEnabled() )
15167       {
15168         mMapCanvas->unsetMapTool( handler->mapTool() );
15169         mActionPan->trigger();
15170       }
15171       else
15172       {
15173         handler->setLayerForTool( layer );
15174       }
15175     }
15176   }
15177 
15178   bool identifyModeIsActiveLayer = QgsSettings().enumValue( QStringLiteral( "/Map/identifyMode" ), QgsMapToolIdentify::ActiveLayer ) == QgsMapToolIdentify::ActiveLayer;
15179 
15180   if ( !layer )
15181   {
15182     mMenuSelect->setEnabled( false );
15183     mActionSelectFeatures->setEnabled( false );
15184     mActionSelectPolygon->setEnabled( false );
15185     mActionSelectFreehand->setEnabled( false );
15186     mActionSelectRadius->setEnabled( false );
15187     mActionIdentify->setEnabled( true );
15188     mActionSelectByExpression->setEnabled( false );
15189     mActionSelectByForm->setEnabled( false );
15190     mActionLabeling->setEnabled( false );
15191     mActionOpenTable->setEnabled( false );
15192     mMenuFilterTable->setEnabled( false );
15193     mActionOpenTableSelected->setEnabled( false );
15194     mActionOpenTableVisible->setEnabled( false );
15195     mActionOpenTableEdited->setEnabled( false );
15196     mActionSelectAll->setEnabled( false );
15197     mActionReselect->setEnabled( false );
15198     mActionInvertSelection->setEnabled( false );
15199     mActionOpenFieldCalc->setEnabled( false );
15200     mActionToggleEditing->setEnabled( false );
15201     mActionToggleEditing->setChecked( false );
15202     mActionSaveLayerEdits->setEnabled( false );
15203     mActionSaveLayerDefinition->setEnabled( false );
15204     mActionLayerSaveAs->setEnabled( false );
15205     mActionLayerProperties->setEnabled( false );
15206     mActionLayerSubsetString->setEnabled( false );
15207     mActionAddToOverview->setEnabled( false );
15208     mActionFeatureAction->setEnabled( false );
15209     mActionAddFeature->setEnabled( false );
15210     mActionCircularStringCurvePoint->setEnabled( false );
15211     mActionCircularStringRadius->setEnabled( false );
15212     mMenuCircle->setEnabled( false );
15213     mActionCircle2Points->setEnabled( false );
15214     mActionCircle3Points->setEnabled( false );
15215     mActionCircle3Tangents->setEnabled( false );
15216     mActionCircle2TangentsPoint->setEnabled( false );
15217     mActionCircleCenterPoint->setEnabled( false );
15218     mMenuEllipse->setEnabled( false );
15219     mActionEllipseCenter2Points->setEnabled( false );
15220     mActionEllipseCenterPoint->setEnabled( false );
15221     mActionEllipseExtent->setEnabled( false );
15222     mActionEllipseFoci->setEnabled( false );
15223     mMenuRectangle->setEnabled( false );
15224     mActionRectangleCenterPoint->setEnabled( false );
15225     mActionRectangleExtent->setEnabled( false );
15226     mActionRectangle3PointsDistance->setEnabled( false );
15227     mActionRectangle3PointsProjected->setEnabled( false );
15228     mMenuRegularPolygon->setEnabled( false );
15229     mActionRegularPolygon2Points->setEnabled( false );
15230     mActionRegularPolygonCenterPoint->setEnabled( false );
15231     mActionRegularPolygonCenterCorner->setEnabled( false );
15232     mMenuEditGeometry->setEnabled( false );
15233     mActionMoveFeature->setEnabled( false );
15234     mActionMoveFeatureCopy->setEnabled( false );
15235     mActionRotateFeature->setEnabled( false );
15236     mActionScaleFeature->setEnabled( false );
15237     mActionOffsetCurve->setEnabled( false );
15238     mActionVertexTool->setEnabled( false );
15239     mActionVertexToolActiveLayer->setEnabled( false );
15240     mActionDeleteSelected->setEnabled( false );
15241     mActionCutFeatures->setEnabled( false );
15242     mActionCopyFeatures->setEnabled( false );
15243     mActionPasteFeatures->setEnabled( false );
15244     mActionCopyStyle->setEnabled( false );
15245     mActionPasteStyle->setEnabled( false );
15246     mActionCopyLayer->setEnabled( false );
15247     // pasting should be allowed if there is a layer in the clipboard
15248     mActionPasteLayer->setEnabled( clipboard()->hasFormat( QStringLiteral( QGSCLIPBOARD_MAPLAYER_MIME ) ) );
15249     mActionReverseLine->setEnabled( false );
15250     mActionTrimExtendFeature->setEnabled( false );
15251 
15252     if ( mUndoDock && mUndoDock->widget() )
15253       mUndoDock->widget()->setEnabled( false );
15254     mActionUndo->setEnabled( false );
15255     mActionRedo->setEnabled( false );
15256     mActionSimplifyFeature->setEnabled( false );
15257     mActionAddRing->setEnabled( false );
15258     mActionFillRing->setEnabled( false );
15259     mActionAddPart->setEnabled( false );
15260     mActionDeleteRing->setEnabled( false );
15261     mActionDeletePart->setEnabled( false );
15262     mActionReshapeFeatures->setEnabled( false );
15263     mActionSplitFeatures->setEnabled( false );
15264     mActionSplitParts->setEnabled( false );
15265     mActionMergeFeatures->setEnabled( false );
15266     mMenuEditAttributes->setEnabled( false );
15267     mActionMergeFeatureAttributes->setEnabled( false );
15268     mActionMultiEditAttributes->setEnabled( false );
15269     mActionRotatePointSymbols->setEnabled( false );
15270     mActionOffsetPointSymbol->setEnabled( false );
15271 
15272     mActionPinLabels->setEnabled( false );
15273     mActionShowHideLabels->setEnabled( false );
15274     mActionMoveLabel->setEnabled( false );
15275     mActionRotateLabel->setEnabled( false );
15276     mActionChangeLabelProperties->setEnabled( false );
15277 
15278     mActionDiagramProperties->setEnabled( false );
15279 
15280     mActionLocalHistogramStretch->setEnabled( false );
15281     mActionFullHistogramStretch->setEnabled( false );
15282     mActionLocalCumulativeCutStretch->setEnabled( false );
15283     mActionFullCumulativeCutStretch->setEnabled( false );
15284     mActionIncreaseBrightness->setEnabled( false );
15285     mActionDecreaseBrightness->setEnabled( false );
15286     mActionIncreaseContrast->setEnabled( false );
15287     mActionDecreaseContrast->setEnabled( false );
15288     mActionIncreaseGamma->setEnabled( false );
15289     mActionDecreaseGamma->setEnabled( false );
15290     mActionPanToSelected->setEnabled( false );
15291     mActionZoomActualSize->setEnabled( false );
15292     mActionZoomToSelected->setEnabled( false );
15293     mActionZoomToLayers->setEnabled( false );
15294     mActionZoomToLayer->setEnabled( false );
15295 
15296     enableMeshEditingTools( false );
15297     enableDigitizeTechniqueActions( false );
15298 
15299     return;
15300   }
15301 
15302   mMenuSelect->setEnabled( true );
15303 
15304   mActionLayerProperties->setEnabled( QgsProject::instance()->layerIsEmbedded( layer->id() ).isEmpty() );
15305   mActionAddToOverview->setEnabled( true );
15306   mActionPanToSelected->setEnabled( true );
15307   mActionZoomToSelected->setEnabled( true );
15308   mActionZoomToLayers->setEnabled( true );
15309   mActionZoomToLayer->setEnabled( true );
15310 
15311   mActionCopyStyle->setEnabled( true );
15312   mActionPasteStyle->setEnabled( clipboard()->hasFormat( QStringLiteral( QGSCLIPBOARD_STYLE_MIME ) ) );
15313   mActionCopyLayer->setEnabled( true );
15314 
15315   // Vector layers
15316   switch ( layer->type() )
15317   {
15318     case QgsMapLayerType::VectorLayer:
15319     {
15320       QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
15321       QgsVectorDataProvider *dprovider = vlayer->dataProvider();
15322       QString addFeatureText;
15323 
15324       bool isEditable = vlayer->isEditable();
15325       bool layerHasSelection = vlayer->selectedFeatureCount() > 0;
15326       bool layerHasActions = !vlayer->actions()->actions( QStringLiteral( "Canvas" ) ).isEmpty() || !QgsGui::mapLayerActionRegistry()->mapLayerActions( vlayer ).isEmpty();
15327       bool isSpatial = vlayer->isSpatial();
15328 
15329       mActionLocalHistogramStretch->setEnabled( false );
15330       mActionFullHistogramStretch->setEnabled( false );
15331       mActionLocalCumulativeCutStretch->setEnabled( false );
15332       mActionFullCumulativeCutStretch->setEnabled( false );
15333       mActionIncreaseBrightness->setEnabled( false );
15334       mActionDecreaseBrightness->setEnabled( false );
15335       mActionIncreaseContrast->setEnabled( false );
15336       mActionDecreaseContrast->setEnabled( false );
15337       mActionIncreaseGamma->setEnabled( false );
15338       mActionDecreaseGamma->setEnabled( false );
15339       mActionZoomActualSize->setEnabled( false );
15340       mActionZoomToLayer->setEnabled( isSpatial );
15341       mActionLabeling->setEnabled( isSpatial );
15342       mActionDiagramProperties->setEnabled( isSpatial );
15343       mActionReverseLine->setEnabled( false );
15344       mActionTrimExtendFeature->setEnabled( false );
15345 
15346       enableMeshEditingTools( false );
15347 
15348       mActionSelectFeatures->setEnabled( isSpatial );
15349       mActionSelectPolygon->setEnabled( isSpatial );
15350       mActionSelectFreehand->setEnabled( isSpatial );
15351       mActionSelectRadius->setEnabled( isSpatial );
15352       mActionIdentify->setEnabled( isSpatial || !identifyModeIsActiveLayer );
15353       mActionSelectByExpression->setEnabled( true );
15354       mActionSelectByForm->setEnabled( true );
15355       mActionOpenTable->setEnabled( true );
15356       mMenuFilterTable->setEnabled( true );
15357       mActionOpenTableSelected->setEnabled( true );
15358       mActionOpenTableVisible->setEnabled( true );
15359       mActionOpenTableEdited->setEnabled( true );
15360       mActionSelectAll->setEnabled( true );
15361       mActionReselect->setEnabled( true );
15362       mActionInvertSelection->setEnabled( true );
15363       mActionSaveLayerDefinition->setEnabled( true );
15364       mActionLayerSaveAs->setEnabled( true );
15365       mActionCopyFeatures->setEnabled( layerHasSelection );
15366       mActionFeatureAction->setEnabled( layerHasActions );
15367 
15368       if ( !isEditable && mMapCanvas && mMapCanvas->mapTool()
15369            && ( mMapCanvas->mapTool()->flags() & QgsMapTool::EditTool ) && !mSaveRollbackInProgress )
15370       {
15371         mMapCanvas->setMapTool( mNonEditMapTool );
15372       }
15373 
15374       if ( dprovider )
15375       {
15376         bool canChangeAttributes = dprovider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
15377         bool canDeleteFeatures = dprovider->capabilities() & QgsVectorDataProvider::DeleteFeatures;
15378         bool canAddFeatures = dprovider->capabilities() & QgsVectorDataProvider::AddFeatures;
15379         bool canChangeGeometry = isSpatial && dprovider->capabilities() & QgsVectorDataProvider::ChangeGeometries;
15380         bool canSupportEditing = vlayer->supportsEditing();
15381 
15382         mActionLayerSubsetString->setEnabled( !isEditable && dprovider->supportsSubsetString() );
15383 
15384         mActionToggleEditing->setEnabled( canSupportEditing );
15385         mActionToggleEditing->setChecked( canSupportEditing && isEditable );
15386         mActionSaveLayerEdits->setEnabled( canSupportEditing && isEditable && vlayer->isModified() );
15387         mUndoDock->widget()->setEnabled( canSupportEditing && isEditable );
15388         mActionUndo->setEnabled( canSupportEditing );
15389         mActionRedo->setEnabled( canSupportEditing );
15390         mMenuEditGeometry->setEnabled( canSupportEditing && isEditable );
15391 
15392         //start editing/stop editing
15393         if ( canSupportEditing )
15394         {
15395           updateUndoActions();
15396         }
15397 
15398         mActionPasteFeatures->setEnabled( isEditable && canAddFeatures && !clipboard()->isEmpty() );
15399 
15400         mActionAddFeature->setEnabled( isEditable && canAddFeatures );
15401 
15402         bool enableCircularTools;
15403         bool enableShapeTools;
15404         enableCircularTools = isEditable && ( canAddFeatures || canChangeGeometry )
15405                               && ( vlayer->geometryType() == QgsWkbTypes::LineGeometry || vlayer->geometryType() == QgsWkbTypes::PolygonGeometry );
15406         enableShapeTools = enableCircularTools;
15407         mActionCircularStringCurvePoint->setEnabled( enableCircularTools );
15408         mActionCircularStringRadius->setEnabled( enableCircularTools );
15409         mMenuCircle->setEnabled( enableShapeTools );
15410         mActionCircle2Points->setEnabled( enableShapeTools );
15411         mActionCircle3Points->setEnabled( enableShapeTools );
15412         mActionCircle3Tangents->setEnabled( enableShapeTools );
15413         mActionCircle2TangentsPoint->setEnabled( enableShapeTools );
15414         mActionCircleCenterPoint->setEnabled( enableShapeTools );
15415         mMenuEllipse->setEnabled( enableShapeTools );
15416         mActionEllipseCenter2Points->setEnabled( enableShapeTools );
15417         mActionEllipseCenterPoint->setEnabled( enableShapeTools );
15418         mActionEllipseExtent->setEnabled( enableShapeTools );
15419         mActionEllipseFoci->setEnabled( enableShapeTools );
15420         mMenuRectangle->setEnabled( enableShapeTools );
15421         mActionRectangleCenterPoint->setEnabled( enableShapeTools );
15422         mActionRectangleExtent->setEnabled( enableShapeTools );
15423         mActionRectangle3PointsDistance->setEnabled( enableShapeTools );
15424         mActionRectangle3PointsProjected->setEnabled( enableShapeTools );
15425         mMenuRegularPolygon->setEnabled( enableShapeTools );
15426         mActionRegularPolygon2Points->setEnabled( enableShapeTools );
15427         mActionRegularPolygonCenterPoint->setEnabled( enableShapeTools );
15428         mActionRegularPolygonCenterCorner->setEnabled( enableShapeTools );
15429 
15430         //does provider allow deleting of features?
15431         mActionDeleteSelected->setEnabled( isEditable && canDeleteFeatures && layerHasSelection );
15432         mActionCutFeatures->setEnabled( isEditable && canDeleteFeatures && layerHasSelection );
15433 
15434         //merge tool needs editable layer and provider with the capability of adding and deleting features
15435         if ( isEditable && canChangeAttributes )
15436         {
15437           mActionMergeFeatures->setEnabled( layerHasSelection && canDeleteFeatures && canAddFeatures );
15438           mMenuEditAttributes->setEnabled( layerHasSelection );
15439           mActionMergeFeatureAttributes->setEnabled( layerHasSelection );
15440           mActionMultiEditAttributes->setEnabled( layerHasSelection );
15441         }
15442         else
15443         {
15444           mActionMergeFeatures->setEnabled( false );
15445           mMenuEditAttributes->setEnabled( false );
15446           mActionMergeFeatureAttributes->setEnabled( false );
15447           mActionMultiEditAttributes->setEnabled( false );
15448         }
15449 
15450         bool isMultiPart = QgsWkbTypes::isMultiType( vlayer->wkbType() ) || !dprovider->doesStrictFeatureTypeCheck();
15451 
15452         // moving enabled if geometry changes are supported
15453         mActionAddPart->setEnabled( isEditable && canChangeGeometry );
15454         mActionDeletePart->setEnabled( isEditable && canChangeGeometry );
15455         mActionMoveFeature->setEnabled( isEditable && canChangeGeometry );
15456         mActionMoveFeatureCopy->setEnabled( isEditable && canChangeGeometry );
15457         mActionRotateFeature->setEnabled( isEditable && canChangeGeometry );
15458         mActionScaleFeature->setEnabled( isEditable && canChangeGeometry );
15459         mActionVertexTool->setEnabled( isEditable && canChangeGeometry );
15460         mActionVertexToolActiveLayer->setEnabled( isEditable && canChangeGeometry );
15461 
15462         enableDigitizeTechniqueActions( isEditable && canChangeGeometry );
15463 
15464         if ( vlayer->geometryType() == QgsWkbTypes::PointGeometry )
15465         {
15466           mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) ) );
15467           addFeatureText = tr( "Add Point Feature" );
15468           mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeaturePoint.svg" ) ) );
15469           mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyPoint.svg" ) ) );
15470 
15471           mActionAddRing->setEnabled( false );
15472           mActionFillRing->setEnabled( false );
15473           mActionReshapeFeatures->setEnabled( false );
15474           mActionSplitFeatures->setEnabled( false );
15475           mActionSplitParts->setEnabled( false );
15476           mActionSimplifyFeature->setEnabled( false );
15477           mActionDeleteRing->setEnabled( false );
15478           mActionRotatePointSymbols->setEnabled( false );
15479           mActionOffsetPointSymbol->setEnabled( false );
15480           mActionOffsetCurve->setEnabled( false );
15481 
15482           if ( isEditable && canChangeAttributes )
15483           {
15484             if ( QgsMapToolRotatePointSymbols::layerIsRotatable( vlayer ) )
15485             {
15486               mActionRotatePointSymbols->setEnabled( true );
15487             }
15488             if ( QgsMapToolOffsetPointSymbol::layerIsOffsetable( vlayer ) )
15489             {
15490               mActionOffsetPointSymbol->setEnabled( true );
15491             }
15492           }
15493         }
15494         else if ( vlayer->geometryType() == QgsWkbTypes::LineGeometry )
15495         {
15496           mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCaptureLine.svg" ) ) );
15497           addFeatureText = tr( "Add Line Feature" );
15498           mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureLine.svg" ) ) );
15499           mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyLine.svg" ) ) );
15500 
15501           mActionReshapeFeatures->setEnabled( isEditable && canChangeGeometry );
15502           mActionSplitFeatures->setEnabled( isEditable && canAddFeatures );
15503           mActionSplitParts->setEnabled( isEditable && canChangeGeometry && isMultiPart );
15504           mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
15505           mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
15506           mActionReverseLine->setEnabled( isEditable && canChangeGeometry );
15507           mActionTrimExtendFeature->setEnabled( isEditable && canChangeGeometry );
15508 
15509           mActionAddRing->setEnabled( false );
15510           mActionFillRing->setEnabled( false );
15511           mActionDeleteRing->setEnabled( false );
15512         }
15513         else if ( vlayer->geometryType() == QgsWkbTypes::PolygonGeometry )
15514         {
15515           mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePolygon.svg" ) ) );
15516           addFeatureText = tr( "Add Polygon Feature" );
15517           mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeature.svg" ) ) );
15518           mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopy.svg" ) ) );
15519 
15520           mActionAddRing->setEnabled( isEditable && canChangeGeometry );
15521           mActionFillRing->setEnabled( isEditable && canChangeGeometry );
15522           mActionReshapeFeatures->setEnabled( isEditable && canChangeGeometry );
15523           mActionSplitFeatures->setEnabled( isEditable && canAddFeatures );
15524           mActionSplitParts->setEnabled( isEditable && canChangeGeometry && isMultiPart );
15525           mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
15526           mActionDeleteRing->setEnabled( isEditable && canChangeGeometry );
15527           mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
15528           mActionTrimExtendFeature->setEnabled( isEditable && canChangeGeometry );
15529         }
15530         else if ( vlayer->geometryType() == QgsWkbTypes::NullGeometry )
15531         {
15532           mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewTableRow.svg" ) ) );
15533           addFeatureText = tr( "Add Record" );
15534           mActionAddRing->setEnabled( false );
15535           mActionFillRing->setEnabled( false );
15536           mActionReshapeFeatures->setEnabled( false );
15537           mActionSplitFeatures->setEnabled( false );
15538           mActionSplitParts->setEnabled( false );
15539           mActionSimplifyFeature->setEnabled( false );
15540           mActionDeleteRing->setEnabled( false );
15541           mActionOffsetCurve->setEnabled( false );
15542         }
15543 
15544         mActionOpenFieldCalc->setEnabled( true );
15545         mActionAddFeature->setText( addFeatureText );
15546         mActionAddFeature->setToolTip( addFeatureText );
15547         QgsGui::shortcutsManager()->unregisterAction( mActionAddFeature );
15548         if ( !mActionAddFeature->text().isEmpty() ) // The text will be empty on unknown geometry type -> in this case do not create a shortcut
15549           QgsGui::shortcutsManager()->registerAction( mActionAddFeature, mActionAddFeature->shortcut().toString() );
15550       }
15551       else
15552       {
15553         mUndoDock->widget()->setEnabled( false );
15554         mActionUndo->setEnabled( false );
15555         mActionRedo->setEnabled( false );
15556         mActionLayerSubsetString->setEnabled( false );
15557       }
15558       break;
15559     }
15560 
15561     case QgsMapLayerType::RasterLayer:
15562     {
15563       const QgsRasterLayer *rlayer = qobject_cast<const QgsRasterLayer *>( layer );
15564       const QgsRasterDataProvider *dprovider = rlayer->dataProvider();
15565 
15566       if ( dprovider
15567            && dprovider->dataType( 1 ) != Qgis::DataType::ARGB32
15568            && dprovider->dataType( 1 ) != Qgis::DataType::ARGB32_Premultiplied )
15569       {
15570         if ( dprovider->capabilities() & QgsRasterDataProvider::Size )
15571         {
15572           mActionFullHistogramStretch->setEnabled( true );
15573         }
15574         else
15575         {
15576           // it would hang up reading the data for WMS for example
15577           mActionFullHistogramStretch->setEnabled( false );
15578         }
15579         mActionLocalHistogramStretch->setEnabled( true );
15580       }
15581       else
15582       {
15583         mActionLocalHistogramStretch->setEnabled( false );
15584         mActionFullHistogramStretch->setEnabled( false );
15585       }
15586 
15587       mActionLocalCumulativeCutStretch->setEnabled( true );
15588       mActionFullCumulativeCutStretch->setEnabled( true );
15589       mActionIncreaseBrightness->setEnabled( true );
15590       mActionDecreaseBrightness->setEnabled( true );
15591       mActionIncreaseContrast->setEnabled( true );
15592       mActionDecreaseContrast->setEnabled( true );
15593       mActionIncreaseGamma->setEnabled( true );
15594       mActionDecreaseGamma->setEnabled( true );
15595 
15596       mActionLayerSubsetString->setEnabled( false );
15597       mActionFeatureAction->setEnabled( false );
15598       mActionSelectFeatures->setEnabled( false );
15599       mActionSelectPolygon->setEnabled( false );
15600       mActionSelectFreehand->setEnabled( false );
15601       mActionSelectRadius->setEnabled( false );
15602       mActionZoomActualSize->setEnabled( true );
15603       mActionZoomToLayer->setEnabled( true );
15604       mActionOpenTable->setEnabled( false );
15605       mMenuFilterTable->setEnabled( false );
15606       mActionOpenTableSelected->setEnabled( false );
15607       mActionOpenTableVisible->setEnabled( false );
15608       mActionOpenTableEdited->setEnabled( false );
15609       mActionSelectAll->setEnabled( false );
15610       mActionReselect->setEnabled( false );
15611       mActionInvertSelection->setEnabled( false );
15612       mActionSelectByExpression->setEnabled( false );
15613       mActionSelectByForm->setEnabled( false );
15614       mActionOpenFieldCalc->setEnabled( false );
15615       mActionToggleEditing->setEnabled( false );
15616       mActionToggleEditing->setChecked( false );
15617       mActionSaveLayerEdits->setEnabled( false );
15618       mUndoDock->widget()->setEnabled( false );
15619       mActionUndo->setEnabled( false );
15620       mActionRedo->setEnabled( false );
15621       mActionSaveLayerDefinition->setEnabled( true );
15622       mActionLayerSaveAs->setEnabled( true );
15623       mActionAddFeature->setEnabled( false );
15624       mActionCircularStringCurvePoint->setEnabled( false );
15625       mActionCircularStringRadius->setEnabled( false );
15626       mMenuCircle->setEnabled( false );
15627       mActionCircle2Points->setEnabled( false );
15628       mActionCircle3Points->setEnabled( false );
15629       mActionCircle3Tangents->setEnabled( false );
15630       mActionCircle2TangentsPoint->setEnabled( false );
15631       mActionCircleCenterPoint->setEnabled( false );
15632       mMenuEllipse->setEnabled( false );
15633       mActionEllipseCenter2Points->setEnabled( false );
15634       mActionEllipseCenterPoint->setEnabled( false );
15635       mActionEllipseExtent->setEnabled( false );
15636       mActionEllipseFoci->setEnabled( false );
15637       mMenuRectangle->setEnabled( false );
15638       mActionRectangleCenterPoint->setEnabled( false );
15639       mActionRectangleExtent->setEnabled( false );
15640       mActionRectangle3PointsDistance->setEnabled( false );
15641       mActionRectangle3PointsProjected->setEnabled( false );
15642       mMenuRegularPolygon->setEnabled( false );
15643       mActionRegularPolygon2Points->setEnabled( false );
15644       mActionRegularPolygonCenterPoint->setEnabled( false );
15645       mActionRegularPolygonCenterCorner->setEnabled( false );
15646       mMenuEditAttributes->setEnabled( false );
15647       mMenuEditGeometry->setEnabled( false );
15648       mActionReverseLine->setEnabled( false );
15649       mActionTrimExtendFeature->setEnabled( false );
15650       mActionDeleteSelected->setEnabled( false );
15651       mActionAddRing->setEnabled( false );
15652       mActionFillRing->setEnabled( false );
15653       mActionAddPart->setEnabled( false );
15654       mActionVertexTool->setEnabled( false );
15655       mActionVertexToolActiveLayer->setEnabled( false );
15656       mActionMoveFeature->setEnabled( false );
15657       mActionMoveFeatureCopy->setEnabled( false );
15658       mActionRotateFeature->setEnabled( false );
15659       mActionScaleFeature->setEnabled( false );
15660       mActionOffsetCurve->setEnabled( false );
15661       mActionCopyFeatures->setEnabled( false );
15662       mActionCutFeatures->setEnabled( false );
15663       mActionPasteFeatures->setEnabled( false );
15664       mActionRotatePointSymbols->setEnabled( false );
15665       mActionOffsetPointSymbol->setEnabled( false );
15666       mActionDeletePart->setEnabled( false );
15667       mActionDeleteRing->setEnabled( false );
15668       mActionSimplifyFeature->setEnabled( false );
15669       mActionReshapeFeatures->setEnabled( false );
15670       mActionSplitFeatures->setEnabled( false );
15671       mActionSplitParts->setEnabled( false );
15672       mActionLabeling->setEnabled( false );
15673       mActionDiagramProperties->setEnabled( false );
15674 
15675       enableMeshEditingTools( false );
15676       enableDigitizeTechniqueActions( false );
15677 
15678       //NOTE: This check does not really add any protection, as it is called on load not on layer select/activate
15679       //If you load a layer with a provider and idenitfy ability then load another without, the tool would be disabled for both
15680 
15681       //Enable the Identify tool ( GDAL datasets draw without a provider )
15682       //but turn off if data provider exists and has no Identify capabilities
15683       mActionIdentify->setEnabled( true );
15684 
15685       if ( identifyModeIsActiveLayer )
15686       {
15687         if ( dprovider )
15688         {
15689           // does provider allow the identify map tool?
15690           if ( dprovider->capabilities() & QgsRasterDataProvider::Identify )
15691           {
15692             mActionIdentify->setEnabled( true );
15693           }
15694           else
15695           {
15696             mActionIdentify->setEnabled( false );
15697           }
15698         }
15699       }
15700       break;
15701     }
15702 
15703     case QgsMapLayerType::MeshLayer:
15704     {
15705       QgsMeshLayer *mlayer = qobject_cast<QgsMeshLayer *>( layer );
15706 
15707       mActionLocalHistogramStretch->setEnabled( false );
15708       mActionFullHistogramStretch->setEnabled( false );
15709       mActionLocalCumulativeCutStretch->setEnabled( false );
15710       mActionFullCumulativeCutStretch->setEnabled( false );
15711       mActionIncreaseBrightness->setEnabled( false );
15712       mActionDecreaseBrightness->setEnabled( false );
15713       mActionIncreaseContrast->setEnabled( false );
15714       mActionDecreaseContrast->setEnabled( false );
15715       mActionIncreaseGamma->setEnabled( false );
15716       mActionDecreaseGamma->setEnabled( false );
15717       mActionLayerSubsetString->setEnabled( false );
15718       mActionFeatureAction->setEnabled( false );
15719       mActionSelectFeatures->setEnabled( false );
15720       mActionSelectPolygon->setEnabled( false );
15721       mActionSelectFreehand->setEnabled( false );
15722       mActionSelectRadius->setEnabled( false );
15723       mActionZoomActualSize->setEnabled( false );
15724       mActionZoomToLayer->setEnabled( true );
15725       mActionOpenTable->setEnabled( false );
15726       mMenuFilterTable->setEnabled( false );
15727       mActionOpenTableSelected->setEnabled( false );
15728       mActionOpenTableVisible->setEnabled( false );
15729       mActionOpenTableEdited->setEnabled( false );
15730       mActionSelectAll->setEnabled( false );
15731       mActionReselect->setEnabled( false );
15732       mActionInvertSelection->setEnabled( false );
15733       mActionSelectByExpression->setEnabled( false );
15734       mActionSelectByForm->setEnabled( false );
15735       mActionOpenFieldCalc->setEnabled( false );
15736       mActionSaveLayerEdits->setEnabled( false );
15737       mActionSaveLayerDefinition->setEnabled( true );
15738       mActionLayerSaveAs->setEnabled( false );
15739       mActionAddFeature->setEnabled( false );
15740       mActionCircularStringCurvePoint->setEnabled( false );
15741       mActionCircularStringRadius->setEnabled( false );
15742       mActionDeleteSelected->setEnabled( false );
15743       mActionAddRing->setEnabled( false );
15744       mActionFillRing->setEnabled( false );
15745       mActionAddPart->setEnabled( false );
15746       mActionVertexTool->setEnabled( false );
15747       mActionVertexToolActiveLayer->setEnabled( false );
15748       mActionMoveFeature->setEnabled( false );
15749       mActionMoveFeatureCopy->setEnabled( false );
15750       mActionRotateFeature->setEnabled( false );
15751       mActionScaleFeature->setEnabled( false );
15752       mActionOffsetCurve->setEnabled( false );
15753       mActionCopyFeatures->setEnabled( false );
15754       mActionCutFeatures->setEnabled( false );
15755       mActionPasteFeatures->setEnabled( false );
15756       mActionRotatePointSymbols->setEnabled( false );
15757       mActionOffsetPointSymbol->setEnabled( false );
15758       mActionDeletePart->setEnabled( false );
15759       mActionDeleteRing->setEnabled( false );
15760       mActionSimplifyFeature->setEnabled( false );
15761       mActionReshapeFeatures->setEnabled( false );
15762       mActionSplitFeatures->setEnabled( false );
15763       mActionSplitParts->setEnabled( false );
15764       mActionLabeling->setEnabled( false );
15765       mActionDiagramProperties->setEnabled( false );
15766       mActionIdentify->setEnabled( true );
15767       enableDigitizeTechniqueActions( false );
15768 
15769       bool canSupportEditing = mlayer->supportsEditing();
15770       bool isEditable = mlayer->isEditable();
15771       mActionToggleEditing->setEnabled( canSupportEditing );
15772       mActionToggleEditing->setChecked( canSupportEditing && isEditable );
15773       mActionSaveLayerEdits->setEnabled( canSupportEditing && isEditable && mlayer->isModified() );
15774       enableMeshEditingTools( isEditable );
15775       mUndoDock->widget()->setEnabled( canSupportEditing && isEditable );
15776       mActionUndo->setEnabled( canSupportEditing && isEditable );
15777       mActionRedo->setEnabled( canSupportEditing && isEditable );
15778       updateUndoActions();
15779     }
15780 
15781     break;
15782 
15783     case QgsMapLayerType::VectorTileLayer:
15784       mActionLocalHistogramStretch->setEnabled( false );
15785       mActionFullHistogramStretch->setEnabled( false );
15786       mActionLocalCumulativeCutStretch->setEnabled( false );
15787       mActionFullCumulativeCutStretch->setEnabled( false );
15788       mActionIncreaseBrightness->setEnabled( false );
15789       mActionDecreaseBrightness->setEnabled( false );
15790       mActionIncreaseContrast->setEnabled( false );
15791       mActionDecreaseContrast->setEnabled( false );
15792       mActionIncreaseGamma->setEnabled( false );
15793       mActionDecreaseGamma->setEnabled( false );
15794       mActionLayerSubsetString->setEnabled( false );
15795       mActionFeatureAction->setEnabled( false );
15796       mActionSelectFeatures->setEnabled( false );
15797       mActionSelectPolygon->setEnabled( false );
15798       mActionSelectFreehand->setEnabled( false );
15799       mActionSelectRadius->setEnabled( false );
15800       mActionZoomActualSize->setEnabled( false );
15801       mActionZoomToLayer->setEnabled( true );
15802       mActionOpenTable->setEnabled( false );
15803       mMenuFilterTable->setEnabled( false );
15804       mActionOpenTableSelected->setEnabled( false );
15805       mActionOpenTableVisible->setEnabled( false );
15806       mActionOpenTableEdited->setEnabled( false );
15807       mActionSelectAll->setEnabled( false );
15808       mActionReselect->setEnabled( false );
15809       mActionInvertSelection->setEnabled( false );
15810       mActionSelectByExpression->setEnabled( false );
15811       mActionSelectByForm->setEnabled( false );
15812       mActionOpenFieldCalc->setEnabled( false );
15813       mActionToggleEditing->setEnabled( false );
15814       mActionToggleEditing->setChecked( false );
15815       mActionSaveLayerEdits->setEnabled( false );
15816       mUndoDock->widget()->setEnabled( false );
15817       mActionUndo->setEnabled( false );
15818       mActionRedo->setEnabled( false );
15819       mActionSaveLayerDefinition->setEnabled( true );
15820       mActionLayerSaveAs->setEnabled( false );
15821       mActionAddFeature->setEnabled( false );
15822       mActionCircularStringCurvePoint->setEnabled( false );
15823       mActionCircularStringRadius->setEnabled( false );
15824       mActionDeleteSelected->setEnabled( false );
15825       mActionAddRing->setEnabled( false );
15826       mActionFillRing->setEnabled( false );
15827       mActionAddPart->setEnabled( false );
15828       mActionVertexTool->setEnabled( false );
15829       mActionVertexToolActiveLayer->setEnabled( false );
15830       mActionMoveFeature->setEnabled( false );
15831       mActionMoveFeatureCopy->setEnabled( false );
15832       mActionRotateFeature->setEnabled( false );
15833       mActionScaleFeature->setEnabled( false );
15834       mActionOffsetCurve->setEnabled( false );
15835       mActionCopyFeatures->setEnabled( false );
15836       mActionCutFeatures->setEnabled( false );
15837       mActionPasteFeatures->setEnabled( false );
15838       mActionRotatePointSymbols->setEnabled( false );
15839       mActionOffsetPointSymbol->setEnabled( false );
15840       mActionDeletePart->setEnabled( false );
15841       mActionDeleteRing->setEnabled( false );
15842       mActionSimplifyFeature->setEnabled( false );
15843       mActionReshapeFeatures->setEnabled( false );
15844       mActionSplitFeatures->setEnabled( false );
15845       mActionSplitParts->setEnabled( false );
15846       mActionLabeling->setEnabled( false );
15847       mActionDiagramProperties->setEnabled( false );
15848       mActionIdentify->setEnabled( true );
15849       enableDigitizeTechniqueActions( false );
15850       enableMeshEditingTools( false );
15851       break;
15852 
15853     case QgsMapLayerType::PointCloudLayer:
15854       mActionLocalHistogramStretch->setEnabled( false );
15855       mActionFullHistogramStretch->setEnabled( false );
15856       mActionLocalCumulativeCutStretch->setEnabled( false );
15857       mActionFullCumulativeCutStretch->setEnabled( false );
15858       mActionIncreaseBrightness->setEnabled( false );
15859       mActionDecreaseBrightness->setEnabled( false );
15860       mActionIncreaseContrast->setEnabled( false );
15861       mActionDecreaseContrast->setEnabled( false );
15862       mActionIncreaseGamma->setEnabled( false );
15863       mActionDecreaseGamma->setEnabled( false );
15864       mActionLayerSubsetString->setEnabled( false );
15865       mActionFeatureAction->setEnabled( false );
15866       mActionSelectFeatures->setEnabled( false );
15867       mActionSelectPolygon->setEnabled( false );
15868       mActionSelectFreehand->setEnabled( false );
15869       mActionSelectRadius->setEnabled( false );
15870       mActionZoomActualSize->setEnabled( false );
15871       mActionZoomToLayer->setEnabled( true );
15872       mActionOpenTable->setEnabled( false );
15873       mMenuFilterTable->setEnabled( false );
15874       mActionOpenTableSelected->setEnabled( false );
15875       mActionOpenTableVisible->setEnabled( false );
15876       mActionOpenTableEdited->setEnabled( false );
15877       mActionSelectAll->setEnabled( false );
15878       mActionReselect->setEnabled( false );
15879       mActionInvertSelection->setEnabled( false );
15880       mActionSelectByExpression->setEnabled( false );
15881       mActionSelectByForm->setEnabled( false );
15882       mActionOpenFieldCalc->setEnabled( false );
15883       mActionToggleEditing->setEnabled( false );
15884       mActionToggleEditing->setChecked( false );
15885       mActionSaveLayerEdits->setEnabled( false );
15886       mUndoDock->widget()->setEnabled( false );
15887       mActionUndo->setEnabled( false );
15888       mActionRedo->setEnabled( false );
15889       mActionSaveLayerDefinition->setEnabled( true );
15890       mActionLayerSaveAs->setEnabled( false );
15891       mActionAddFeature->setEnabled( false );
15892       mActionCircularStringCurvePoint->setEnabled( false );
15893       mActionCircularStringRadius->setEnabled( false );
15894       mActionDeleteSelected->setEnabled( false );
15895       mActionAddRing->setEnabled( false );
15896       mActionFillRing->setEnabled( false );
15897       mActionAddPart->setEnabled( false );
15898       mActionVertexTool->setEnabled( false );
15899       mActionVertexToolActiveLayer->setEnabled( false );
15900       mActionMoveFeature->setEnabled( false );
15901       mActionMoveFeatureCopy->setEnabled( false );
15902       mActionRotateFeature->setEnabled( false );
15903       mActionScaleFeature->setEnabled( false );
15904       mActionOffsetCurve->setEnabled( false );
15905       mActionCopyFeatures->setEnabled( false );
15906       mActionCutFeatures->setEnabled( false );
15907       mActionPasteFeatures->setEnabled( false );
15908       mActionRotatePointSymbols->setEnabled( false );
15909       mActionOffsetPointSymbol->setEnabled( false );
15910       mActionDeletePart->setEnabled( false );
15911       mActionDeleteRing->setEnabled( false );
15912       mActionSimplifyFeature->setEnabled( false );
15913       mActionReshapeFeatures->setEnabled( false );
15914       mActionSplitFeatures->setEnabled( false );
15915       mActionSplitParts->setEnabled( false );
15916       mActionLabeling->setEnabled( false );
15917       mActionDiagramProperties->setEnabled( false );
15918       mActionIdentify->setEnabled( true );
15919       enableDigitizeTechniqueActions( false );
15920       enableMeshEditingTools( false );
15921       break;
15922 
15923     case QgsMapLayerType::PluginLayer:
15924       break;
15925 
15926     case QgsMapLayerType::AnnotationLayer:
15927     {
15928       mActionLocalHistogramStretch->setEnabled( false );
15929       mActionFullHistogramStretch->setEnabled( false );
15930       mActionLocalCumulativeCutStretch->setEnabled( false );
15931       mActionFullCumulativeCutStretch->setEnabled( false );
15932       mActionIncreaseBrightness->setEnabled( false );
15933       mActionDecreaseBrightness->setEnabled( false );
15934       mActionIncreaseContrast->setEnabled( false );
15935       mActionDecreaseContrast->setEnabled( false );
15936       mActionIncreaseGamma->setEnabled( false );
15937       mActionDecreaseGamma->setEnabled( false );
15938       mActionLayerSubsetString->setEnabled( false );
15939       mActionFeatureAction->setEnabled( false );
15940       mActionSelectFeatures->setEnabled( false );
15941       mActionSelectPolygon->setEnabled( false );
15942       mActionSelectFreehand->setEnabled( false );
15943       mActionSelectRadius->setEnabled( false );
15944       mActionZoomActualSize->setEnabled( false );
15945       mActionZoomToLayer->setEnabled( true );
15946       mActionOpenTable->setEnabled( false );
15947       mMenuFilterTable->setEnabled( false );
15948       mActionOpenTableSelected->setEnabled( false );
15949       mActionOpenTableVisible->setEnabled( false );
15950       mActionOpenTableEdited->setEnabled( false );
15951       mActionSelectAll->setEnabled( false );
15952       mActionReselect->setEnabled( false );
15953       mActionInvertSelection->setEnabled( false );
15954       mActionSelectByExpression->setEnabled( false );
15955       mActionSelectByForm->setEnabled( false );
15956       mActionOpenFieldCalc->setEnabled( false );
15957       mActionSaveLayerEdits->setEnabled( false );
15958       mUndoDock->widget()->setEnabled( false );
15959       mActionSaveLayerDefinition->setEnabled( false );
15960       mActionLayerSaveAs->setEnabled( false );
15961       mActionAddFeature->setEnabled( false );
15962       mActionCircularStringCurvePoint->setEnabled( false );
15963       mActionCircularStringRadius->setEnabled( false );
15964       mActionDeleteSelected->setEnabled( false );
15965       mActionAddRing->setEnabled( false );
15966       mActionFillRing->setEnabled( false );
15967       mActionAddPart->setEnabled( false );
15968       mActionVertexTool->setEnabled( false );
15969       mActionVertexToolActiveLayer->setEnabled( false );
15970       mActionMoveFeature->setEnabled( false );
15971       mActionMoveFeatureCopy->setEnabled( false );
15972       mActionRotateFeature->setEnabled( false );
15973       mActionScaleFeature->setEnabled( false );
15974       mActionOffsetCurve->setEnabled( false );
15975       mActionCopyFeatures->setEnabled( false );
15976       mActionCutFeatures->setEnabled( false );
15977       mActionPasteFeatures->setEnabled( false );
15978       mActionRotatePointSymbols->setEnabled( false );
15979       mActionOffsetPointSymbol->setEnabled( false );
15980       mActionDeletePart->setEnabled( false );
15981       mActionDeleteRing->setEnabled( false );
15982       mActionSimplifyFeature->setEnabled( false );
15983       mActionReshapeFeatures->setEnabled( false );
15984       mActionSplitFeatures->setEnabled( false );
15985       mActionSplitParts->setEnabled( false );
15986       mActionLabeling->setEnabled( false );
15987       mActionDiagramProperties->setEnabled( false );
15988       mActionIdentify->setEnabled( true );
15989       enableDigitizeTechniqueActions( true );
15990       mActionToggleEditing->setEnabled( false );
15991       mActionToggleEditing->setChecked( true ); // always editable
15992       mActionUndo->setEnabled( false );
15993       mActionRedo->setEnabled( false );
15994       updateUndoActions();
15995       break;
15996     }
15997   }
15998 
15999   refreshFeatureActions();
16000 }
16001 
refreshActionFeatureAction()16002 void QgisApp::refreshActionFeatureAction()
16003 {
16004   mActionFeatureAction->setEnabled( false );
16005   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
16006   if ( !vlayer )
16007     return;
16008 
16009   bool layerHasActions = !vlayer->actions()->actions( QStringLiteral( "Canvas" ) ).isEmpty() || !QgsGui::mapLayerActionRegistry()->mapLayerActions( vlayer ).isEmpty();
16010   mActionFeatureAction->setEnabled( layerHasActions );
16011 }
16012 
renameView()16013 void QgisApp::renameView()
16014 {
16015   QgsMapCanvasDockWidget *view = qobject_cast< QgsMapCanvasDockWidget * >( sender() );
16016   if ( !view )
16017     return;
16018 
16019   // calculate existing names
16020   QStringList names;
16021   const auto canvases = mapCanvases();
16022   for ( QgsMapCanvas *canvas : canvases )
16023   {
16024     if ( canvas == view->mapCanvas() )
16025       continue;
16026 
16027     names << canvas->objectName();
16028   }
16029 
16030   QString currentName = view->mapCanvas()->objectName();
16031 
16032   QgsNewNameDialog renameDlg( currentName, currentName, QStringList(), names, Qt::CaseSensitive, this );
16033   renameDlg.setWindowTitle( tr( "Map Views" ) );
16034   //renameDlg.setHintString( tr( "Name of the new view" ) );
16035   renameDlg.setOverwriteEnabled( false );
16036   renameDlg.setConflictingNameWarning( tr( "A view with this name already exists" ) );
16037   renameDlg.buttonBox()->addButton( QDialogButtonBox::Help );
16038   connect( renameDlg.buttonBox(), &QDialogButtonBox::helpRequested, this, [ = ]
16039   {
16040     QgsHelp::openHelp( QStringLiteral( "introduction/qgis_gui.html#map-view" ) );
16041   } );
16042 
16043   if ( renameDlg.exec() || renameDlg.name().isEmpty() )
16044   {
16045     QString newName = renameDlg.name();
16046     view->setWindowTitle( newName );
16047     view->mapCanvas()->setObjectName( newName );
16048   }
16049 }
16050 
addRasterLayer(QString const & uri,QString const & baseName,QString const & providerKey)16051 QgsRasterLayer *QgisApp::addRasterLayer( QString const &uri, QString const &baseName, QString const &providerKey )
16052 {
16053   return addLayerPrivate< QgsRasterLayer >( QgsMapLayerType::RasterLayer, uri, baseName, !providerKey.isEmpty() ? providerKey : QLatin1String( "gdal" ), true );
16054 }
16055 
addRasterLayers(QStringList const & files,bool guiWarning)16056 bool QgisApp::addRasterLayers( QStringList const &files, bool guiWarning )
16057 {
16058   if ( files.empty() )
16059   {
16060     return false;
16061   }
16062 
16063   QgsCanvasRefreshBlocker refreshBlocker;
16064 
16065   // this is messy since some files in the list may be rasters and others may
16066   // be ogr layers. We'll set returnValue to false if one or more layers fail
16067   // to load.
16068   bool returnValue = true;
16069   for ( const QString &src : files )
16070   {
16071     QString errMsg;
16072     bool ok = false;
16073 
16074     // if needed prompt for zipitem layers
16075     QString vsiPrefix = QgsZipItem::vsiPrefix( src );
16076     if ( ( !src.startsWith( QLatin1String( "/vsi" ), Qt::CaseInsensitive ) || src.endsWith( QLatin1String( ".zip" ) ) || src.endsWith( QLatin1String( ".tar" ) ) ) &&
16077          ( vsiPrefix == QLatin1String( "/vsizip/" ) || vsiPrefix == QLatin1String( "/vsitar/" ) ) )
16078     {
16079       if ( askUserForZipItemLayers( src, { QgsMapLayerType::RasterLayer } ) )
16080         continue;
16081     }
16082 
16083     const bool isVsiCurl { src.startsWith( QLatin1String( "/vsicurl" ), Qt::CaseInsensitive ) };
16084     const auto scheme { QUrl( src ).scheme() };
16085     const bool isRemoteUrl { src.startsWith( QLatin1String( "http" ) ) || src == QLatin1String( "ftp" ) };
16086 
16087     std::unique_ptr< QgsTemporaryCursorOverride > cursorOverride;
16088     if ( isVsiCurl || isRemoteUrl )
16089     {
16090       cursorOverride = std::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
16091       visibleMessageBar()->pushInfo( tr( "Remote layer" ), tr( "loading %1, please wait …" ).arg( src ) );
16092       qApp->processEvents();
16093     }
16094 
16095     if ( QgsRasterLayer::isValidRasterFileName( src, errMsg ) )
16096     {
16097       QFileInfo myFileInfo( src );
16098 
16099       // set the layer name to the file base name unless provided explicitly
16100       QString layerName;
16101       const QVariantMap uriDetails = QgsProviderRegistry::instance()->decodeUri( QStringLiteral( "gdal" ), src );
16102       if ( !uriDetails[ QStringLiteral( "layerName" ) ].toString().isEmpty() )
16103       {
16104         layerName = uriDetails[ QStringLiteral( "layerName" ) ].toString();
16105       }
16106       else
16107       {
16108         layerName = QgsProviderUtils::suggestLayerNameFromFilePath( src );
16109       }
16110 
16111       // try to create the layer
16112       cursorOverride.reset();
16113       QgsRasterLayer *layer = addLayerPrivate< QgsRasterLayer >( QgsMapLayerType::RasterLayer, src, layerName, QStringLiteral( "gdal" ), guiWarning );
16114 
16115       if ( layer && layer->isValid() )
16116       {
16117         //only allow one copy of a ai grid file to be loaded at a
16118         //time to prevent the user selecting all adfs in 1 dir which
16119         //actually represent 1 coverage,
16120 
16121         if ( myFileInfo.fileName().endsWith( QLatin1String( ".adf" ), Qt::CaseInsensitive ) )
16122         {
16123           break;
16124         }
16125       }
16126       // if layer is invalid addLayerPrivate() will show the error
16127 
16128     } // valid raster filename
16129     else
16130     {
16131       ok = false;
16132 
16133       // Issue message box warning unless we are loading from cmd line since
16134       // non-rasters are passed to this function first and then successfully
16135       // loaded afterwards (see main.cpp)
16136       if ( guiWarning )
16137       {
16138         QString msg = tr( "%1 is not a supported raster data source" ).arg( src );
16139         if ( !errMsg.isEmpty() )
16140           msg += '\n' + errMsg;
16141 
16142         visibleMessageBar()->pushMessage( tr( "Unsupported Data Source" ), msg, Qgis::MessageLevel::Critical );
16143       }
16144     }
16145     if ( ! ok )
16146     {
16147       returnValue = false;
16148     }
16149   }
16150   return returnValue;
16151 }
16152 
addPluginLayer(const QString & uri,const QString & baseName,const QString & providerKey)16153 QgsPluginLayer *QgisApp::addPluginLayer( const QString &uri, const QString &baseName, const QString &providerKey )
16154 {
16155   QgsPluginLayer *layer = QgsApplication::pluginLayerRegistry()->createLayer( providerKey, uri );
16156   if ( !layer )
16157     return nullptr;
16158 
16159   layer->setName( baseName );
16160 
16161   QgsProject::instance()->addMapLayer( layer );
16162 
16163   return layer;
16164 }
16165 
16166 
16167 
16168 #ifdef ANDROID
keyReleaseEvent(QKeyEvent * event)16169 void QgisApp::keyReleaseEvent( QKeyEvent *event )
16170 {
16171   static bool sAccepted = true;
16172   if ( event->key() == Qt::Key_Close )
16173   {
16174     // do something useful here
16175     int ret = QMessageBox::question( this, tr( "Exit QGIS" ),
16176                                      tr( "Do you really want to quit QGIS?" ),
16177                                      QMessageBox::Yes | QMessageBox::No );
16178     switch ( ret )
16179     {
16180       case QMessageBox::Yes:
16181         this->close();
16182         break;
16183 
16184       case QMessageBox::No:
16185         break;
16186     }
16187     event->setAccepted( sAccepted ); // don't close my Top Level Widget !
16188     sAccepted = false;// close the app next time when the user press back button
16189   }
16190   else
16191   {
16192     QMainWindow::keyReleaseEvent( event );
16193   }
16194 }
16195 #endif
16196 
keyPressEvent(QKeyEvent * e)16197 void QgisApp::keyPressEvent( QKeyEvent *e )
16198 {
16199   emit keyPressed( e );
16200 
16201 #if 0 && defined(_MSC_VER) && defined(QGISDEBUG)
16202   if ( e->key() == Qt::Key_Backslash && e->modifiers() == Qt::ControlModifier )
16203   {
16204     QgsCrashHandler::handle( 0 );
16205   }
16206 #endif
16207 
16208   //cancel rendering progress with esc key
16209   if ( e->key() == Qt::Key_Escape )
16210   {
16211     stopRendering();
16212   }
16213   else
16214   {
16215     e->ignore();
16216   }
16217 }
16218 
newProfile()16219 void QgisApp::newProfile()
16220 {
16221   QString text = QInputDialog::getText( this, tr( "New profile name" ), tr( "New profile name" ) );
16222   if ( text.isEmpty() )
16223     return;
16224 
16225   userProfileManager()->createUserProfile( text );
16226   userProfileManager()->loadUserProfile( text );
16227 }
16228 
onTaskCompleteShowNotify(long taskId,int status)16229 void QgisApp::onTaskCompleteShowNotify( long taskId, int status )
16230 {
16231   if ( status == QgsTask::Complete || status == QgsTask::Terminated )
16232   {
16233     long long minTime = QgsSettings().value( QStringLiteral( "minTaskLengthForSystemNotification" ), 5, QgsSettings::App ).toLongLong() * 1000;
16234     QgsTask *task = QgsApplication::taskManager()->task( taskId );
16235     if ( task && task->elapsedTime() >= minTime )
16236     {
16237       if ( status == QgsTask::Complete )
16238         showSystemNotification( tr( "Task complete" ), task->description() );
16239       else if ( status == QgsTask::Terminated )
16240         showSystemNotification( tr( "Task failed" ), task->description() );
16241     }
16242   }
16243 }
16244 
onTransactionGroupsChanged()16245 void QgisApp::onTransactionGroupsChanged()
16246 {
16247   const auto groups = QgsProject::instance()->transactionGroups();
16248   for ( auto it = groups.constBegin(); it != groups.constEnd(); ++it )
16249   {
16250     connect( it.value(), &QgsTransactionGroup::commitError, this, &QgisApp::transactionGroupCommitError, Qt::UniqueConnection );
16251   }
16252 }
16253 
onSnappingConfigChanged()16254 void QgisApp::onSnappingConfigChanged()
16255 {
16256   mSnappingUtils->setConfig( QgsProject::instance()->snappingConfig() );
16257 }
16258 
createPreviewImage(const QString & path,const QIcon & icon)16259 void QgisApp::createPreviewImage( const QString &path, const QIcon &icon )
16260 {
16261   // Render the map canvas
16262   QSize previewSize( 250, 177 ); // h = w / std::sqrt(2)
16263   QRect previewRect( QPoint( ( mMapCanvas->width() - previewSize.width() ) / 2
16264                              , ( mMapCanvas->height() - previewSize.height() ) / 2 )
16265                      , previewSize );
16266 
16267   QPixmap previewImage( previewSize );
16268   previewImage.fill();
16269   QPainter previewPainter( &previewImage );
16270   mMapCanvas->render( &previewPainter, QRect( QPoint(), previewSize ), previewRect );
16271 
16272   if ( !icon.isNull() )
16273   {
16274     QPixmap pixmap = icon.pixmap( QSize( 24, 24 ) );
16275     previewPainter.drawPixmap( QPointF( 250 - 24 - 5, 177 - 24 - 5 ), pixmap );
16276   }
16277   previewPainter.end();
16278 
16279   // Save
16280   previewImage.save( path );
16281 }
16282 
startProfile(const QString & name)16283 void QgisApp::startProfile( const QString &name )
16284 {
16285   QgsApplication::profiler()->start( name );
16286 }
16287 
endProfile()16288 void QgisApp::endProfile()
16289 {
16290   QgsApplication::profiler()->end();
16291 }
16292 
functionProfile(void (QgisApp::* fnc)(),QgisApp * instance,const QString & name)16293 void QgisApp::functionProfile( void ( QgisApp::*fnc )(), QgisApp *instance, const QString &name )
16294 {
16295   QgsScopedRuntimeProfile profile( name );
16296   ( instance->*fnc )();
16297 }
16298 
mapCanvas_keyPressed(QKeyEvent * e)16299 void QgisApp::mapCanvas_keyPressed( QKeyEvent *e )
16300 {
16301   // Delete selected features when it is possible and KeyEvent was not managed by current MapTool
16302   if ( ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) && e->isAccepted() )
16303   {
16304     deleteSelected( nullptr, nullptr, true );
16305   }
16306 }
16307 
customProjection()16308 void QgisApp::customProjection()
16309 {
16310   // Create an instance of the Custom Projection Designer modeless dialog.
16311   // Autodelete the dialog when closing since a pointer is not retained.
16312   QgsCustomProjectionDialog *myDialog = new QgsCustomProjectionDialog( this );
16313   myDialog->setAttribute( Qt::WA_DeleteOnClose );
16314   myDialog->show();
16315 }
16316 
newBookmark(bool inProject)16317 void QgisApp::newBookmark( bool inProject )
16318 {
16319   QgsBookmark bookmark;
16320   bookmark.setName( tr( "New bookmark" ) );
16321   bookmark.setExtent( QgsReferencedRectangle( mapCanvas()->extent(), mapCanvas()->mapSettings().destinationCrs() ) );
16322   QgsBookmarkEditorDialog *dlg = new QgsBookmarkEditorDialog( bookmark, inProject, this, mapCanvas() );
16323   dlg->setAttribute( Qt::WA_DeleteOnClose );
16324   dlg->show();
16325 }
16326 
showBookmarks()16327 void QgisApp::showBookmarks()
16328 {
16329   mBrowserWidget->setUserVisible( true );
16330   QModelIndex index = browserModel()->findPath( QStringLiteral( "bookmarks:" ) );
16331   mBrowserWidget->browserWidget()->setActiveIndex( index );
16332 }
16333 
showBookmarkManager(bool show)16334 void QgisApp::showBookmarkManager( bool show )
16335 {
16336   mBookMarksDockWidget->setUserVisible( show );
16337 }
16338 
getBookmarkIndexMap()16339 QMap<QString, QModelIndex> QgisApp::getBookmarkIndexMap()
16340 {
16341   return mBookMarksDockWidget->getIndexMap();
16342 }
16343 
zoomToBookmarkIndex(const QModelIndex & index)16344 void QgisApp::zoomToBookmarkIndex( const QModelIndex &index )
16345 {
16346   mBookMarksDockWidget->zoomToBookmarkIndex( index );
16347 }
16348 
identifyMapTool() const16349 QgsMapToolIdentifyAction *QgisApp::identifyMapTool() const
16350 {
16351   return mMapTools->mapTool< QgsMapToolIdentifyAction >( QgsAppMapTools::Identify );
16352 }
16353 
takeAppScreenShots(const QString & saveDirectory,const int categories)16354 void QgisApp::takeAppScreenShots( const QString &saveDirectory, const int categories )
16355 {
16356   QgsAppScreenShots ass( saveDirectory );
16357   ass.takePicturesOf( QgsAppScreenShots::Categories( categories ) );
16358 }
16359 
16360 // Slot that gets called when the project file was saved with an older
16361 // version of QGIS
16362 
oldProjectVersionWarning(const QString & oldVersion)16363 void QgisApp::oldProjectVersionWarning( const QString &oldVersion )
16364 {
16365   Q_UNUSED( oldVersion )
16366   QgsSettings settings;
16367 
16368   if ( settings.value( QStringLiteral( "qgis/warnOldProjectVersion" ), QVariant( true ) ).toBool() )
16369   {
16370     QString smalltext = tr( "This project file was saved by QGIS version %1."
16371                             " When saving this project file, QGIS will update it to version %2, "
16372                             "possibly rendering it useless for older versions of QGIS." ).arg( oldVersion, Qgis::version() );
16373 
16374     QString title = tr( "Project file is older" );
16375 
16376     visibleMessageBar()->pushMessage( title, smalltext );
16377   }
16378 }
16379 
updateUndoActions()16380 void QgisApp::updateUndoActions()
16381 {
16382   bool canUndo = false, canRedo = false;
16383   QgsMapLayer *layer = activeLayer();
16384   if ( layer  && layer->isEditable() )
16385   {
16386     canUndo = layer->undoStack()->canUndo();
16387     canRedo = layer->undoStack()->canRedo();
16388   }
16389   mActionUndo->setEnabled( canUndo );
16390   mActionRedo->setEnabled( canRedo );
16391 }
16392 
16393 
16394 // add project directory to python path
projectChanged(const QDomDocument & doc)16395 void QgisApp::projectChanged( const QDomDocument &doc )
16396 {
16397   Q_UNUSED( doc )
16398   QgsProject *project = qobject_cast<QgsProject *>( sender() );
16399   if ( !project )
16400     return;
16401 
16402   QFileInfo fi( project->fileName() );
16403   if ( !fi.exists() )
16404     return;
16405 
16406   static QString sPrevProjectDir = QString();
16407 
16408   if ( sPrevProjectDir == fi.canonicalPath() )
16409     return;
16410 
16411   QString expr;
16412   if ( !sPrevProjectDir.isNull() )
16413   {
16414     QString prev = sPrevProjectDir;
16415     expr = QStringLiteral( "sys.path.remove(u'%1'); " ).arg( prev.replace( '\'', QLatin1String( "\\'" ) ) );
16416   }
16417 
16418   sPrevProjectDir = fi.canonicalPath();
16419 
16420   QString prev = sPrevProjectDir;
16421   expr += QStringLiteral( "sys.path.append(u'%1')" ).arg( prev.replace( '\'', QLatin1String( "\\'" ) ) );
16422 
16423   QgsPythonRunner::run( expr );
16424 }
16425 
writeProject(QDomDocument & doc)16426 void QgisApp::writeProject( QDomDocument &doc )
16427 {
16428   // QGIS server does not use QgsProject for loading of QGIS project.
16429   // In order to allow reading of new projects, let's also write the original <legend> tag to the project.
16430   // Ideally the server should be ported to new layer tree implementation, but that requires
16431   // non-trivial changes to the server components.
16432   // The <legend> tag is ignored by QGIS application in >= 2.4 and this way also the new project files
16433   // can be opened in older versions of QGIS without losing information about layer groups.
16434 
16435   QgsLayerTree *clonedRoot = QgsProject::instance()->layerTreeRoot()->clone();
16436   QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) );
16437   QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), QgsProject::instance() ); // convert absolute paths to relative paths if required
16438   QDomElement oldLegendElem = QgsLayerTreeUtils::writeOldLegend( doc, QgsLayerTree::toGroup( clonedRoot ),
16439                               clonedRoot->hasCustomLayerOrder(), clonedRoot->customLayerOrder() );
16440   delete clonedRoot;
16441   QDomElement qgisNode = doc.firstChildElement( QStringLiteral( "qgis" ) );
16442   qgisNode.appendChild( oldLegendElem );
16443 
16444   QgsProject::instance()->writeEntry( QStringLiteral( "Legend" ), QStringLiteral( "filterByMap" ), static_cast< bool >( layerTreeView()->layerTreeModel()->legendFilterMapSettings() ) );
16445 
16446   // Save the position of the map view docks
16447   QDomElement mapViewNode = doc.createElement( QStringLiteral( "mapViewDocks" ) );
16448   const auto dockWidgets = findChildren< QgsMapCanvasDockWidget * >();
16449   for ( QgsMapCanvasDockWidget *w : dockWidgets )
16450   {
16451     QDomElement node = doc.createElement( QStringLiteral( "view" ) );
16452     node.setAttribute( QStringLiteral( "name" ), w->mapCanvas()->objectName() );
16453     node.setAttribute( QStringLiteral( "synced" ), w->isViewCenterSynchronized() );
16454     node.setAttribute( QStringLiteral( "showCursor" ), w->isCursorMarkerVisible() );
16455     node.setAttribute( QStringLiteral( "showExtent" ), w->isMainCanvasExtentVisible() );
16456     node.setAttribute( QStringLiteral( "scaleSynced" ), w->isViewScaleSynchronized() );
16457     node.setAttribute( QStringLiteral( "scaleFactor" ), w->scaleFactor() );
16458     node.setAttribute( QStringLiteral( "showLabels" ), w->labelsVisible() );
16459     node.setAttribute( QStringLiteral( "zoomSelected" ), w->isAutoZoomToSelected() );
16460     writeDockWidgetSettings( w, node );
16461     mapViewNode.appendChild( node );
16462   }
16463   qgisNode.appendChild( mapViewNode );
16464 
16465 #ifdef HAVE_3D
16466   QgsReadWriteContext readWriteContext;
16467   readWriteContext.setPathResolver( QgsProject::instance()->pathResolver() );
16468   QDomElement elem3DMaps = doc.createElement( QStringLiteral( "mapViewDocks3D" ) );
16469   const QList< Qgs3DMapCanvasDockWidget * > docks = findChildren<Qgs3DMapCanvasDockWidget *>();
16470   for ( Qgs3DMapCanvasDockWidget *w : docks )
16471   {
16472     QDomElement elem3DMap = doc.createElement( QStringLiteral( "view" ) );
16473     elem3DMap.setAttribute( QStringLiteral( "name" ), w->mapCanvas3D()->objectName() );
16474     QDomElement elem3DMapSettings = w->mapCanvas3D()->map()->writeXml( doc, readWriteContext );
16475     elem3DMap.appendChild( elem3DMapSettings );
16476     QDomElement elemCamera = w->mapCanvas3D()->cameraController()->writeXml( doc );
16477     elem3DMap.appendChild( elemCamera );
16478     QDomElement elemAnimation = w->animationWidget()->animation().writeXml( doc );
16479     elem3DMap.appendChild( elemAnimation );
16480     writeDockWidgetSettings( w, elem3DMap );
16481     elem3DMaps.appendChild( elem3DMap );
16482   }
16483   qgisNode.appendChild( elem3DMaps );
16484 #endif
16485 
16486   projectChanged( doc );
16487 }
16488 
writeDockWidgetSettings(QDockWidget * dockWidget,QDomElement & elem)16489 void QgisApp::writeDockWidgetSettings( QDockWidget *dockWidget, QDomElement &elem )
16490 {
16491   elem.setAttribute( QStringLiteral( "x" ), dockWidget->x() );
16492   elem.setAttribute( QStringLiteral( "y" ), dockWidget->y() );
16493   elem.setAttribute( QStringLiteral( "width" ), dockWidget->width() );
16494   elem.setAttribute( QStringLiteral( "height" ), dockWidget->height() );
16495   elem.setAttribute( QStringLiteral( "floating" ), dockWidget->isFloating() );
16496   elem.setAttribute( QStringLiteral( "area" ), dockWidgetArea( dockWidget ) );
16497 }
16498 
askUserForDatumTransform(const QgsCoordinateReferenceSystem & sourceCrs,const QgsCoordinateReferenceSystem & destinationCrs,const QgsMapLayer * layer)16499 bool QgisApp::askUserForDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsMapLayer *layer )
16500 {
16501   Q_ASSERT( qApp->thread() == QThread::currentThread() );
16502 
16503   QString title;
16504   if ( layer )
16505   {
16506     // try to make a user-friendly (short!) identifier for the layer
16507     QString layerIdentifier;
16508     if ( !layer->name().isEmpty() )
16509     {
16510       layerIdentifier = layer->name();
16511     }
16512     else
16513     {
16514       const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
16515       if ( parts.contains( QStringLiteral( "path" ) ) )
16516       {
16517         const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );
16518         layerIdentifier = fi.fileName();
16519       }
16520       else if ( layer->dataProvider() )
16521       {
16522         const QgsDataSourceUri uri( layer->source() );
16523         layerIdentifier = uri.table();
16524       }
16525     }
16526     if ( !layerIdentifier.isEmpty() )
16527       title = tr( "Select Transformation for %1" ).arg( layerIdentifier );
16528   }
16529 
16530   return QgsDatumTransformDialog::run( sourceCrs, destinationCrs, this, mMapCanvas, title );
16531 }
16532 
readDockWidgetSettings(QDockWidget * dockWidget,const QDomElement & elem)16533 void QgisApp::readDockWidgetSettings( QDockWidget *dockWidget, const QDomElement &elem )
16534 {
16535   int x = elem.attribute( QStringLiteral( "x" ), QStringLiteral( "0" ) ).toInt();
16536   int y = elem.attribute( QStringLiteral( "y" ), QStringLiteral( "0" ) ).toInt();
16537   int w = elem.attribute( QStringLiteral( "width" ), QStringLiteral( "400" ) ).toInt();
16538   int h = elem.attribute( QStringLiteral( "height" ), QStringLiteral( "400" ) ).toInt();
16539   bool floating = elem.attribute( QStringLiteral( "floating" ), QStringLiteral( "0" ) ).toInt();
16540   Qt::DockWidgetArea area = static_cast< Qt::DockWidgetArea >( elem.attribute( QStringLiteral( "area" ), QString::number( Qt::RightDockWidgetArea ) ).toInt() );
16541 
16542   setupDockWidget( dockWidget, floating, QRect( x, y, w, h ), area );
16543 }
16544 
16545 
readProject(const QDomDocument & doc)16546 void QgisApp::readProject( const QDomDocument &doc )
16547 {
16548   projectChanged( doc );
16549 
16550   // force update of canvas, without automatic changes to extent and OTF projections
16551   bool autoSetupOnFirstLayer = mLayerTreeCanvasBridge->autoSetupOnFirstLayer();
16552   mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( false );
16553 
16554   mLayerTreeCanvasBridge->setCanvasLayers();
16555 
16556   if ( autoSetupOnFirstLayer )
16557     mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( true );
16558 
16559   QDomNodeList nodes = doc.elementsByTagName( QStringLiteral( "mapViewDocks" ) );
16560   QList< QgsMapCanvas * > views;
16561   if ( !nodes.isEmpty() )
16562   {
16563     QDomNode viewNode = nodes.at( 0 );
16564     nodes = viewNode.childNodes();
16565     for ( int i = 0; i < nodes.size(); ++i )
16566     {
16567       QDomElement elementNode = nodes.at( i ).toElement();
16568       QString mapName = elementNode.attribute( QStringLiteral( "name" ) );
16569       bool synced = elementNode.attribute( QStringLiteral( "synced" ), QStringLiteral( "0" ) ).toInt();
16570       bool showCursor = elementNode.attribute( QStringLiteral( "showCursor" ), QStringLiteral( "0" ) ).toInt();
16571       bool showExtent = elementNode.attribute( QStringLiteral( "showExtent" ), QStringLiteral( "0" ) ).toInt();
16572       bool scaleSynced = elementNode.attribute( QStringLiteral( "scaleSynced" ), QStringLiteral( "0" ) ).toInt();
16573       double scaleFactor = elementNode.attribute( QStringLiteral( "scaleFactor" ), QStringLiteral( "1" ) ).toDouble();
16574       bool showLabels = elementNode.attribute( QStringLiteral( "showLabels" ), QStringLiteral( "1" ) ).toInt();
16575       bool zoomSelected = elementNode.attribute( QStringLiteral( "zoomSelected" ), QStringLiteral( "0" ) ).toInt();
16576 
16577       QgsMapCanvasDockWidget *mapCanvasDock = createNewMapCanvasDock( mapName );
16578       readDockWidgetSettings( mapCanvasDock, elementNode );
16579       QgsMapCanvas *mapCanvas = mapCanvasDock->mapCanvas();
16580       mapCanvasDock->setViewCenterSynchronized( synced );
16581       mapCanvasDock->setCursorMarkerVisible( showCursor );
16582       mapCanvasDock->setScaleFactor( scaleFactor );
16583       mapCanvasDock->setViewScaleSynchronized( scaleSynced );
16584       mapCanvasDock->setMainCanvasExtentVisible( showExtent );
16585       mapCanvasDock->setLabelsVisible( showLabels );
16586       mapCanvasDock->setAutoZoomToSelected( zoomSelected );
16587       mapCanvas->readProject( doc );
16588       views << mapCanvas;
16589     }
16590   }
16591 
16592 #ifdef HAVE_3D
16593   QgsReadWriteContext readWriteContext;
16594   readWriteContext.setPathResolver( QgsProject::instance()->pathResolver() );
16595   QDomElement elem3DMaps = doc.documentElement().firstChildElement( QStringLiteral( "mapViewDocks3D" ) );
16596   if ( !elem3DMaps.isNull() )
16597   {
16598     QDomElement elem3DMap = elem3DMaps.firstChildElement( QStringLiteral( "view" ) );
16599     while ( !elem3DMap.isNull() )
16600     {
16601       QString mapName = elem3DMap.attribute( QStringLiteral( "name" ) );
16602 
16603       Qgs3DMapCanvasDockWidget *mapCanvasDock3D = createNew3DMapCanvasDock( mapName );
16604       readDockWidgetSettings( mapCanvasDock3D, elem3DMap );
16605 
16606       QDomElement elem3D = elem3DMap.firstChildElement( QStringLiteral( "qgis3d" ) );
16607       Qgs3DMapSettings *map = new Qgs3DMapSettings;
16608       map->readXml( elem3D, readWriteContext );
16609       map->resolveReferences( *QgsProject::instance() );
16610 
16611       map->setTransformContext( QgsProject::instance()->transformContext() );
16612       map->setPathResolver( QgsProject::instance()->pathResolver() );
16613       map->setMapThemeCollection( QgsProject::instance()->mapThemeCollection() );
16614       connect( QgsProject::instance(), &QgsProject::transformContextChanged, map, [map]
16615       {
16616         map->setTransformContext( QgsProject::instance()->transformContext() );
16617       } );
16618 
16619       // these things are not saved in project
16620       map->setSelectionColor( mMapCanvas->selectionColor() );
16621       map->setBackgroundColor( mMapCanvas->canvasColor() );
16622       if ( map->terrainGenerator() && map->terrainGenerator()->type() == QgsTerrainGenerator::Flat )
16623       {
16624         QgsFlatTerrainGenerator *flatTerrainGen = static_cast<QgsFlatTerrainGenerator *>( map->terrainGenerator() );
16625         flatTerrainGen->setExtent( mMapCanvas->projectExtent() );
16626       }
16627       map->setOutputDpi( QgsApplication::desktop()->logicalDpiX() );
16628 
16629       mapCanvasDock3D->setMapSettings( map );
16630 
16631       QDomElement elemCamera = elem3DMap.firstChildElement( QStringLiteral( "camera" ) );
16632       if ( !elemCamera.isNull() )
16633       {
16634         mapCanvasDock3D->mapCanvas3D()->cameraController()->readXml( elemCamera );
16635       }
16636 
16637       QDomElement elemAnimation = elem3DMap.firstChildElement( QStringLiteral( "animation3d" ) );
16638       if ( !elemAnimation.isNull() )
16639       {
16640         Qgs3DAnimationSettings animationSettings;
16641         animationSettings.readXml( elemAnimation );
16642         mapCanvasDock3D->animationWidget()->setAnimation( animationSettings );
16643       }
16644 
16645       elem3DMap = elem3DMap.nextSiblingElement( QStringLiteral( "view" ) );
16646     }
16647   }
16648 #endif
16649 
16650   // unfreeze all new views at once. We don't do this as they are created since additional
16651   // views which may exist in project could rearrange the docks and cause the canvases to resize
16652   // resulting in multiple redraws
16653   const auto constViews = views;
16654   for ( QgsMapCanvas *c : constViews )
16655   {
16656     c->freeze( false );
16657   }
16658 }
16659 
showLayerProperties(QgsMapLayer * mapLayer,const QString & page)16660 void QgisApp::showLayerProperties( QgsMapLayer *mapLayer, const QString &page )
16661 {
16662   /*
16663   TODO: Consider reusing the property dialogs again.
16664   Sometimes around mid 2005, the property dialogs were saved for later reuse;
16665   this resulted in a time savings when reopening the dialog. The code below
16666   cannot be used as is, however, simply by saving the dialog pointer here.
16667   Either the map layer needs to be passed as an argument to sync or else
16668   a separate copy of the dialog pointer needs to be stored with each layer.
16669   */
16670 
16671   if ( !mapLayer )
16672     return;
16673 
16674   if ( !QgsProject::instance()->layerIsEmbedded( mapLayer->id() ).isEmpty() )
16675   {
16676     return; //don't show properties of embedded layers
16677   }
16678 
16679   // collect factories from registered data providers
16680   QList<const QgsMapLayerConfigWidgetFactory *> providerFactories = QgsGui::providerGuiRegistry()->mapLayerConfigWidgetFactories( mapLayer );
16681   providerFactories.append( mMapLayerPanelFactories );
16682 
16683   switch ( mapLayer->type() )
16684   {
16685     case QgsMapLayerType::RasterLayer:
16686     {
16687       QgsRasterLayerProperties *rasterLayerPropertiesDialog = new QgsRasterLayerProperties( mapLayer, mMapCanvas, this );
16688 
16689       for ( const QgsMapLayerConfigWidgetFactory *factory : std::as_const( providerFactories ) )
16690       {
16691         rasterLayerPropertiesDialog->addPropertiesPageFactory( factory );
16692       }
16693 
16694       if ( !page.isEmpty() )
16695         rasterLayerPropertiesDialog->setCurrentPage( page );
16696       else
16697         rasterLayerPropertiesDialog->restoreLastPage();
16698 
16699       // Cannot use exec here due to raster transparency map tool:
16700       // in order to pass focus to the canvas, the dialog needs to
16701       // be hidden and shown in non-modal mode.
16702       rasterLayerPropertiesDialog->setModal( true );
16703       rasterLayerPropertiesDialog->show();
16704       // Delete (later, for safety) since dialog cannot be reused without
16705       // updating code
16706       connect( rasterLayerPropertiesDialog, &QgsRasterLayerProperties::accepted, [ rasterLayerPropertiesDialog ]
16707       {
16708         rasterLayerPropertiesDialog->deleteLater();
16709       } );
16710       connect( rasterLayerPropertiesDialog, &QgsRasterLayerProperties::rejected, [ rasterLayerPropertiesDialog ]
16711       {
16712         rasterLayerPropertiesDialog->deleteLater();
16713       } );
16714       break;
16715     }
16716 
16717     case QgsMapLayerType::MeshLayer:
16718     {
16719       QgsMeshLayerProperties meshLayerPropertiesDialog( mapLayer, mMapCanvas, this );
16720 
16721       for ( const QgsMapLayerConfigWidgetFactory *factory : std::as_const( providerFactories ) )
16722       {
16723         meshLayerPropertiesDialog.addPropertiesPageFactory( factory );
16724       }
16725 
16726       if ( !page.isEmpty() )
16727         meshLayerPropertiesDialog.setCurrentPage( page );
16728       else
16729         meshLayerPropertiesDialog.restoreLastPage();
16730 
16731       mMapStyleWidget->blockUpdates( true );
16732       if ( meshLayerPropertiesDialog.exec() )
16733       {
16734         activateDeactivateLayerRelatedActions( mapLayer );
16735         mMapStyleWidget->updateCurrentWidgetLayer();
16736       }
16737       mMapStyleWidget->blockUpdates( false ); // delete since dialog cannot be reused without updating code
16738       break;
16739     }
16740 
16741     case QgsMapLayerType::VectorLayer:
16742     {
16743       QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer );
16744 
16745       QgsVectorLayerProperties *vectorLayerPropertiesDialog = new QgsVectorLayerProperties( mMapCanvas, visibleMessageBar(), vlayer, this );
16746       connect(
16747         vectorLayerPropertiesDialog, static_cast<void ( QgsVectorLayerProperties::* )( QgsMapLayer * )>( &QgsVectorLayerProperties::toggleEditing ),
16748       this, [ = ]( QgsMapLayer * layer ) { toggleEditing( layer ); }
16749       );
16750       connect( vectorLayerPropertiesDialog, &QgsVectorLayerProperties::exportAuxiliaryLayer, this, [ = ]( QgsAuxiliaryLayer * layer )
16751       {
16752         if ( layer )
16753         {
16754           std::unique_ptr<QgsVectorLayer> clone;
16755           clone.reset( layer->toSpatialLayer() );
16756 
16757           saveAsFile( clone.get() );
16758         }
16759       } );
16760       for ( const QgsMapLayerConfigWidgetFactory *factory : std::as_const( providerFactories ) )
16761       {
16762         vectorLayerPropertiesDialog->addPropertiesPageFactory( factory );
16763       }
16764 
16765       if ( !page.isEmpty() )
16766         vectorLayerPropertiesDialog->setCurrentPage( page );
16767       else
16768         vectorLayerPropertiesDialog->restoreLastPage();
16769 
16770       mMapStyleWidget->blockUpdates( true );
16771       if ( vectorLayerPropertiesDialog->exec() )
16772       {
16773         activateDeactivateLayerRelatedActions( mapLayer );
16774         mMapStyleWidget->updateCurrentWidgetLayer();
16775       }
16776       mMapStyleWidget->blockUpdates( false );
16777 
16778       delete vectorLayerPropertiesDialog; // delete since dialog cannot be reused without updating code
16779       break;
16780     }
16781 
16782     case QgsMapLayerType::VectorTileLayer:
16783     {
16784       QgsVectorTileLayerProperties vectorTileLayerPropertiesDialog( qobject_cast<QgsVectorTileLayer *>( mapLayer ), mMapCanvas, visibleMessageBar(), this );
16785       if ( !page.isEmpty() )
16786         vectorTileLayerPropertiesDialog.setCurrentPage( page );
16787       else
16788         vectorTileLayerPropertiesDialog.restoreLastPage();
16789 
16790       mMapStyleWidget->blockUpdates( true );
16791       if ( vectorTileLayerPropertiesDialog.exec() )
16792       {
16793         activateDeactivateLayerRelatedActions( mapLayer );
16794         mMapStyleWidget->updateCurrentWidgetLayer();
16795       }
16796       mMapStyleWidget->blockUpdates( false ); // delete since dialog cannot be reused without updating code
16797       break;
16798     }
16799 
16800     case QgsMapLayerType::PointCloudLayer:
16801     {
16802       QgsPointCloudLayerProperties pointCloudLayerPropertiesDialog( qobject_cast<QgsPointCloudLayer *>( mapLayer ), mMapCanvas, visibleMessageBar(), this );
16803 
16804       if ( !page.isEmpty() )
16805         pointCloudLayerPropertiesDialog.setCurrentPage( page );
16806       else
16807         pointCloudLayerPropertiesDialog.restoreLastPage();
16808 
16809       for ( const QgsMapLayerConfigWidgetFactory *factory : std::as_const( providerFactories ) )
16810       {
16811         pointCloudLayerPropertiesDialog.addPropertiesPageFactory( factory );
16812       }
16813 
16814       mMapStyleWidget->blockUpdates( true );
16815       if ( pointCloudLayerPropertiesDialog.exec() )
16816       {
16817         activateDeactivateLayerRelatedActions( mapLayer );
16818         mMapStyleWidget->updateCurrentWidgetLayer();
16819       }
16820       mMapStyleWidget->blockUpdates( false ); // delete since dialog cannot be reused without updating code
16821       break;
16822     }
16823 
16824     case QgsMapLayerType::PluginLayer:
16825     {
16826       QgsPluginLayer *pl = qobject_cast<QgsPluginLayer *>( mapLayer );
16827       if ( !pl )
16828         return;
16829 
16830       QgsPluginLayerType *plt = QgsApplication::pluginLayerRegistry()->pluginLayerType( pl->pluginLayerType() );
16831       if ( !plt )
16832         return;
16833 
16834       if ( !plt->showLayerProperties( pl ) )
16835       {
16836         visibleMessageBar()->pushMessage( tr( "Warning" ),
16837                                           tr( "This layer doesn't have a properties dialog." ),
16838                                           Qgis::MessageLevel::Info );
16839       }
16840       break;
16841     }
16842 
16843     case QgsMapLayerType::AnnotationLayer:
16844     {
16845       QgsAnnotationLayerProperties annotationLayerPropertiesDialog( qobject_cast<QgsAnnotationLayer *>( mapLayer ), mMapCanvas, visibleMessageBar(), this );
16846 
16847       if ( !page.isEmpty() )
16848         annotationLayerPropertiesDialog.setCurrentPage( page );
16849       else
16850         annotationLayerPropertiesDialog.restoreLastPage();
16851 
16852       for ( const QgsMapLayerConfigWidgetFactory *factory : std::as_const( providerFactories ) )
16853       {
16854         annotationLayerPropertiesDialog.addPropertiesPageFactory( factory );
16855       }
16856 
16857       mMapStyleWidget->blockUpdates( true );
16858       if ( annotationLayerPropertiesDialog.exec() )
16859       {
16860         activateDeactivateLayerRelatedActions( mapLayer );
16861         mMapStyleWidget->updateCurrentWidgetLayer();
16862       }
16863       mMapStyleWidget->blockUpdates( false ); // delete since dialog cannot be reused without updating code
16864       break;
16865     }
16866 
16867   }
16868 }
16869 
namSetup()16870 void QgisApp::namSetup()
16871 {
16872   QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance();
16873 
16874   connect( nam, &QNetworkAccessManager::proxyAuthenticationRequired,
16875            this, &QgisApp::namProxyAuthenticationRequired );
16876 
16877   connect( nam, qOverload< QgsNetworkRequestParameters >( &QgsNetworkAccessManager::requestTimedOut ),
16878            this, &QgisApp::namRequestTimedOut );
16879 
16880   nam->setAuthHandler( std::make_unique<QgsAppAuthRequestHandler>() );
16881 #ifndef QT_NO_SSL
16882   nam->setSslErrorHandler( std::make_unique<QgsAppSslErrorHandler>() );
16883 #endif
16884 }
16885 
namProxyAuthenticationRequired(const QNetworkProxy & proxy,QAuthenticator * auth)16886 void QgisApp::namProxyAuthenticationRequired( const QNetworkProxy &proxy, QAuthenticator *auth )
16887 {
16888   QgsSettings settings;
16889   if ( !settings.value( QStringLiteral( "proxy/proxyEnabled" ), false ).toBool() ||
16890        settings.value( QStringLiteral( "proxy/proxyType" ), "" ).toString() == QLatin1String( "DefaultProxy" ) )
16891   {
16892     auth->setUser( QString() );
16893     return;
16894   }
16895 
16896   QString username = auth->user();
16897   QString password = auth->password();
16898 
16899   for ( ;; )
16900   {
16901     bool ok = QgsCredentials::instance()->get(
16902                 QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
16903                 username, password,
16904                 tr( "Proxy authentication required" ) );
16905     if ( !ok )
16906       return;
16907 
16908     if ( auth->user() != username || ( password != auth->password() && !password.isNull() ) )
16909     {
16910       QgsCredentials::instance()->put(
16911         QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
16912         username, password
16913       );
16914       break;
16915     }
16916     else
16917     {
16918       // credentials didn't change - stored ones probably wrong? clear password and retry
16919       QgsCredentials::instance()->put(
16920         QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
16921         username, QString() );
16922     }
16923   }
16924 
16925   auth->setUser( username );
16926   auth->setPassword( password );
16927 }
16928 
namRequestTimedOut(const QgsNetworkRequestParameters & request)16929 void QgisApp::namRequestTimedOut( const QgsNetworkRequestParameters &request )
16930 {
16931   QLabel *msgLabel = new QLabel( tr( "Network request to %1 timed out, any data received is likely incomplete." ).arg( request.request().url().host() ) +
16932                                  tr( " Please check the <a href=\"#messageLog\">message log</a> for further info." ), messageBar() );
16933   msgLabel->setWordWrap( true );
16934   connect( msgLabel, &QLabel::linkActivated, mLogDock, &QWidget::show );
16935   messageBar()->pushItem( new QgsMessageBarItem( msgLabel, Qgis::MessageLevel::Warning, QgsMessageBar::defaultMessageTimeout() ) );
16936 }
16937 
namUpdate()16938 void QgisApp::namUpdate()
16939 {
16940   QgsNetworkAccessManager::instance()->setupDefaultProxyAndCache();
16941 }
16942 
masterPasswordSetup()16943 void QgisApp::masterPasswordSetup()
16944 {
16945   connect( QgsApplication::authManager(), &QgsAuthManager::messageOut,
16946            this, &QgisApp::authMessageOut );
16947   connect( QgsApplication::authManager(), &QgsAuthManager::passwordHelperMessageOut,
16948            this, &QgisApp::authMessageOut );
16949   connect( QgsApplication::authManager(), &QgsAuthManager::authDatabaseEraseRequested,
16950            this, &QgisApp::eraseAuthenticationDatabase );
16951 }
16952 
eraseAuthenticationDatabase()16953 void QgisApp::eraseAuthenticationDatabase()
16954 {
16955   // First check if now is a good time to interact with the user, e.g. project is done loading.
16956   // If not, ask QgsAuthManager to re-emit authDatabaseEraseRequested from the schedule timer.
16957   // No way to know if user interaction will interfere with plugins loading layers.
16958 
16959   if ( !QgsProject::instance()->fileName().isNull() ) // a non-blank project is loaded
16960   {
16961     // Apparently, as of QGIS 2.9, the only way to query that the project is in a
16962     // layer-loading state is via a custom property of the project's layer tree.
16963     QgsLayerTreeGroup *layertree( QgsProject::instance()->layerTreeRoot() );
16964     if ( layertree && layertree->customProperty( QStringLiteral( "loading" ) ).toBool() )
16965     {
16966       QgsDebugMsgLevel( QStringLiteral( "Project loading, skipping auth db erase" ), 2 );
16967       QgsApplication::authManager()->setScheduledAuthDatabaseEraseRequestEmitted( false );
16968       return;
16969     }
16970   }
16971 
16972   // TODO: Check if Browser panel is also still loading?
16973   //       It has auto-connections in parallel (if tree item is expanded), though
16974   //       such connections with possible master password requests *should* be ignored
16975   //       when there is an authentication db erase scheduled.
16976 
16977   // This function should tell QgsAuthManager to stop any erase db schedule timer,
16978   // *after* interacting with the user
16979   QgsAuthGuiUtils::eraseAuthenticationDatabase( messageBar(), this );
16980 }
16981 
authMessageOut(const QString & message,const QString & authtag,QgsAuthManager::MessageLevel level)16982 void QgisApp::authMessageOut( const QString &message, const QString &authtag, QgsAuthManager::MessageLevel level )
16983 {
16984   // Use system notifications if the main window is not the active one,
16985   // push message to the message bar if the main window is active
16986   if ( qApp->activeWindow() != this )
16987   {
16988     showSystemNotification( tr( "QGIS Authentication" ), message );
16989   }
16990   else
16991   {
16992     int levelint = static_cast< int >( level );
16993     visibleMessageBar()->pushMessage( authtag, message, static_cast< Qgis::MessageLevel >( levelint ) );
16994   }
16995 }
16996 
completeInitialization()16997 void QgisApp::completeInitialization()
16998 {
16999   emit initializationCompleted();
17000 }
17001 
toolButtonActionTriggered(QAction * action)17002 void QgisApp::toolButtonActionTriggered( QAction *action )
17003 {
17004   QToolButton *bt = qobject_cast<QToolButton *>( sender() );
17005   if ( !bt )
17006     return;
17007 
17008   QgsSettings settings;
17009   if ( action == mActionSelectFeatures )
17010     settings.setValue( QStringLiteral( "UI/selectTool" ), 1 );
17011   else if ( action == mActionSelectRadius )
17012     settings.setValue( QStringLiteral( "UI/selectTool" ), 2 );
17013   else if ( action == mActionSelectPolygon )
17014     settings.setValue( QStringLiteral( "UI/selectTool" ), 3 );
17015   else if ( action == mActionSelectFreehand )
17016     settings.setValue( QStringLiteral( "UI/selectTool" ), 4 );
17017   else if ( action == mActionSelectByForm )
17018     settings.setValue( QStringLiteral( "UI/selectionTool" ), 0 );
17019   else if ( action == mActionSelectByExpression )
17020     settings.setValue( QStringLiteral( "UI/selectionTool" ), 1 );
17021   else if ( action == mActionSelectAll )
17022     settings.setValue( QStringLiteral( "UI/selectionTool" ), 2 );
17023   else if ( action == mActionInvertSelection )
17024     settings.setValue( QStringLiteral( "UI/selectionTool" ), 3 );
17025   else if ( action == mActionDeselectAll )
17026     settings.setValue( QStringLiteral( "UI/deselectionTool" ), 0 );
17027   else if ( action == mActionDeselectActiveLayer )
17028     settings.setValue( QStringLiteral( "UI/deselectionTool" ), 1 );
17029   else if ( action == mActionOpenTable )
17030     settings.setValue( QStringLiteral( "UI/openTableTool" ), 0 );
17031   else if ( action == mActionOpenTableSelected )
17032     settings.setValue( QStringLiteral( "UI/openTableTool" ), 1 );
17033   else if ( action == mActionOpenTableVisible )
17034     settings.setValue( QStringLiteral( "UI/openTableTool" ), 2 );
17035   else if ( action == mActionOpenTableEdited )
17036     settings.setValue( QStringLiteral( "UI/openTableTool" ), 3 );
17037   else if ( action == mActionMeasure )
17038     settings.setValue( QStringLiteral( "UI/measureTool" ), 0 );
17039   else if ( action == mActionMeasureArea )
17040     settings.setValue( QStringLiteral( "UI/measureTool" ), 1 );
17041   else if ( action == mActionMeasureAngle )
17042     settings.setValue( QStringLiteral( "UI/measureTool" ), 2 );
17043   else if ( action == mActionTextAnnotation )
17044     settings.setValue( QStringLiteral( "UI/annotationTool" ), 0 );
17045   else if ( action == mActionFormAnnotation )
17046     settings.setValue( QStringLiteral( "UI/annotationTool" ), 1 );
17047   else if ( action == mActionHtmlAnnotation )
17048     settings.setValue( QStringLiteral( "UI/annotationTool" ), 2 );
17049   else if ( action == mActionSvgAnnotation )
17050     settings.setValue( QStringLiteral( "UI/annotationTool" ), 3 );
17051   else if ( action == mActionAnnotation )
17052     settings.setValue( QStringLiteral( "UI/annotationTool" ), 4 );
17053   else if ( action == mActionNewSpatiaLiteLayer )
17054     settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 0 );
17055   else if ( action == mActionNewVectorLayer )
17056     settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 1 );
17057   else if ( action == mActionNewMemoryLayer )
17058     settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 2 );
17059   else if ( action == mActionNewGeoPackageLayer )
17060     settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 3 );
17061   else if ( action == mActionRotatePointSymbols )
17062     settings.setValue( QStringLiteral( "UI/defaultPointSymbolAction" ), 0 );
17063   else if ( action == mActionOffsetPointSymbol )
17064     settings.setValue( QStringLiteral( "UI/defaultPointSymbolAction" ), 1 );
17065   else if ( mActionAddPgLayer && action == mActionAddPgLayer )
17066     settings.setValue( QStringLiteral( "UI/defaultAddDbLayerAction" ), 0 );
17067   else if ( mActionAddMssqlLayer && action == mActionAddMssqlLayer )
17068     settings.setValue( QStringLiteral( "UI/defaultAddDbLayerAction" ), 1 );
17069   else if ( mActionAddOracleLayer && action == mActionAddOracleLayer )
17070     settings.setValue( QStringLiteral( "UI/defaultAddDbLayerAction" ), 2 );
17071   else if ( mActionAddHanaLayer && action == mActionAddHanaLayer )
17072     settings.setValue( QStringLiteral( "UI/defaultAddDbLayerAction" ), 3 );
17073   else if ( action == mActionMoveFeature )
17074     settings.setValue( QStringLiteral( "UI/defaultMoveTool" ), 0 );
17075   else if ( action == mActionMoveFeatureCopy )
17076     settings.setValue( QStringLiteral( "UI/defaultMoveTool" ), 1 );
17077   else if ( action == mActionVertexTool )
17078     settings.setEnumValue( QStringLiteral( "UI/defaultVertexTool" ), QgsVertexTool::AllLayers );
17079   else if ( action == mActionVertexToolActiveLayer )
17080     settings.setEnumValue( QStringLiteral( "UI/defaultVertexTool" ), QgsVertexTool::ActiveLayer );
17081   else if ( action == mActionCircularStringCurvePoint )
17082     settings.setValue( QStringLiteral( "UI/defaultCircularString" ), 0 );
17083   else if ( action == mActionCircularStringRadius )
17084     settings.setValue( QStringLiteral( "UI/defaultCircularString" ), 1 );
17085   else if ( action == mActionCircle2Points )
17086     settings.setValue( QStringLiteral( "UI/defaultCircle" ), 0 );
17087   else if ( action == mActionCircle3Points )
17088     settings.setValue( QStringLiteral( "UI/defaultCircle" ), 1 );
17089   else if ( action == mActionCircle3Tangents )
17090     settings.setValue( QStringLiteral( "UI/defaultCircle" ), 2 );
17091   else if ( action == mActionCircle2TangentsPoint )
17092     settings.setValue( QStringLiteral( "UI/defaultCircle" ), 3 );
17093   else if ( action == mActionCircleCenterPoint )
17094     settings.setValue( QStringLiteral( "UI/defaultCircle" ), 4 );
17095   else if ( action == mActionEllipseCenter2Points )
17096     settings.setValue( QStringLiteral( "UI/defaultEllipse" ), 0 );
17097   else if ( action == mActionEllipseCenterPoint )
17098     settings.setValue( QStringLiteral( "UI/defaultEllipse" ), 1 );
17099   else if ( action == mActionEllipseExtent )
17100     settings.setValue( QStringLiteral( "UI/defaultEllipse" ), 2 );
17101   else if ( action == mActionEllipseFoci )
17102     settings.setValue( QStringLiteral( "UI/defaultEllipse" ), 3 );
17103   else if ( action == mActionRectangleCenterPoint )
17104     settings.setValue( QStringLiteral( "UI/defaultRectangle" ), 0 );
17105   else if ( action == mActionRectangleExtent )
17106     settings.setValue( QStringLiteral( "UI/defaultRectangle" ), 1 );
17107   else if ( action == mActionRectangle3PointsDistance )
17108     settings.setValue( QStringLiteral( "UI/defaultRectangle" ), 2 );
17109   else if ( action == mActionRectangle3PointsProjected )
17110     settings.setValue( QStringLiteral( "UI/defaultRectangle" ), 3 );
17111   else if ( action == mActionRegularPolygon2Points )
17112     settings.setValue( QStringLiteral( "UI/defaultRegularPolygon" ), 0 );
17113   else if ( action == mActionRegularPolygonCenterPoint )
17114     settings.setValue( QStringLiteral( "UI/defaultRegularPolygon" ), 1 );
17115   else if ( action == mActionRegularPolygonCenterCorner )
17116     settings.setValue( QStringLiteral( "UI/defaultRegularPolygon" ), 2 );
17117 
17118   bt->setDefaultAction( action );
17119 }
17120 
createPopupMenu()17121 QMenu *QgisApp::createPopupMenu()
17122 {
17123   QMenu *menu = QMainWindow::createPopupMenu();
17124   QList< QAction * > al = menu->actions();
17125   QList< QAction * > panels, toolbars;
17126 
17127   if ( !al.isEmpty() )
17128   {
17129     bool found = false;
17130     for ( int i = 0; i < al.size(); ++i )
17131     {
17132       if ( al[ i ]->isSeparator() )
17133       {
17134         found = true;
17135         continue;
17136       }
17137 
17138       if ( !found )
17139       {
17140         panels.append( al[ i ] );
17141       }
17142       else
17143       {
17144         toolbars.append( al[ i ] );
17145       }
17146     }
17147 
17148     std::sort( panels.begin(), panels.end(), cmpByText_ );
17149     QWidgetAction *panelstitle = new QWidgetAction( menu );
17150     QLabel *plabel = new QLabel( QStringLiteral( "<b>%1</b>" ).arg( tr( "Panels" ) ) );
17151     plabel->setMargin( 3 );
17152     plabel->setAlignment( Qt::AlignHCenter );
17153     panelstitle->setDefaultWidget( plabel );
17154     menu->addAction( panelstitle );
17155     const auto constPanels = panels;
17156     for ( QAction *a : constPanels )
17157     {
17158       if ( !a->property( "fixed_title" ).toBool() )
17159       {
17160         // append " Panel" to menu text. Only ever do this once, because the actions are not unique to
17161         // this single popup menu
17162 
17163         a->setText( tr( "%1 Panel" ).arg( a->text() ) );
17164         a->setProperty( "fixed_title", true );
17165       }
17166       menu->addAction( a );
17167     }
17168     menu->addSeparator();
17169     QWidgetAction *toolbarstitle = new QWidgetAction( menu );
17170     QLabel *tlabel = new QLabel( QStringLiteral( "<b>%1</b>" ).arg( tr( "Toolbars" ) ) );
17171     tlabel->setMargin( 3 );
17172     tlabel->setAlignment( Qt::AlignHCenter );
17173     toolbarstitle->setDefaultWidget( tlabel );
17174     menu->addAction( toolbarstitle );
17175     std::sort( toolbars.begin(), toolbars.end(), cmpByText_ );
17176     const auto constToolbars = toolbars;
17177     for ( QAction *a : constToolbars )
17178     {
17179       menu->addAction( a );
17180     }
17181   }
17182 
17183   return menu;
17184 }
17185 
17186 
showSystemNotification(const QString & title,const QString & message,bool replaceExisting)17187 void QgisApp::showSystemNotification( const QString &title, const QString &message, bool replaceExisting )
17188 {
17189   static QVariant sLastMessageId;
17190 
17191   QgsNative::NotificationSettings settings;
17192   settings.transient = true;
17193   if ( replaceExisting )
17194     settings.messageId = sLastMessageId;
17195   settings.svgAppIconPath = QgsApplication::iconsPath() + QStringLiteral( "qgis_icon.svg" );
17196   settings.pngAppIconPath = QgsApplication::appIconPath();
17197 
17198   QgsNative::NotificationResult result = QgsGui::instance()->nativePlatformInterface()->showDesktopNotification( title, message, settings );
17199 
17200   if ( !result.successful )
17201   {
17202     // fallback - use message bar if available, otherwise use a message log
17203     if ( auto *lMessageBar = messageBar() )
17204     {
17205       lMessageBar->pushInfo( title, message );
17206     }
17207     else
17208     {
17209       QgsMessageLog::logMessage( QStringLiteral( "%1: %2" ).arg( title, message ) );
17210     }
17211   }
17212   else
17213   {
17214     sLastMessageId = result.messageId;
17215   }
17216 }
17217 
onLayerError(const QString & msg)17218 void QgisApp::onLayerError( const QString &msg )
17219 {
17220   QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( sender() );
17221 
17222   Q_ASSERT( layer );
17223 
17224   visibleMessageBar()->pushCritical( tr( "Layer %1" ).arg( layer->name() ), msg );
17225 }
17226 
gestureEvent(QGestureEvent * event)17227 bool QgisApp::gestureEvent( QGestureEvent *event )
17228 {
17229 #ifdef Q_OS_ANDROID
17230   if ( QGesture *tapAndHold = event->gesture( Qt::TapAndHoldGesture ) )
17231   {
17232     tapAndHoldTriggered( static_cast<QTapAndHoldGesture *>( tapAndHold ) );
17233   }
17234   return true;
17235 #else
17236   Q_UNUSED( event )
17237   return false;
17238 #endif
17239 }
17240 
tapAndHoldTriggered(QTapAndHoldGesture * gesture)17241 void QgisApp::tapAndHoldTriggered( QTapAndHoldGesture *gesture )
17242 {
17243   if ( gesture->state() == Qt::GestureFinished )
17244   {
17245     QPoint pos = gesture->position().toPoint();
17246     QWidget *receiver = QApplication::widgetAt( pos );
17247     qDebug() << "tapAndHoldTriggered: LONG CLICK gesture happened at " << pos;
17248     qDebug() << "widget under point of click: " << receiver;
17249 
17250     QApplication::postEvent( receiver, new QMouseEvent( QEvent::MouseButtonPress, receiver->mapFromGlobal( pos ), Qt::RightButton, Qt::RightButton, Qt::NoModifier ) );
17251     QApplication::postEvent( receiver, new QMouseEvent( QEvent::MouseButtonRelease, receiver->mapFromGlobal( pos ), Qt::RightButton, Qt::RightButton, Qt::NoModifier ) );
17252   }
17253 }
17254 
transactionGroupCommitError(const QString & error)17255 void QgisApp::transactionGroupCommitError( const QString &error )
17256 {
17257   displayMessage( tr( "Transaction" ), error, Qgis::MessageLevel::Critical );
17258 }
17259 
duplicateFeatures(QgsMapLayer * mlayer,const QgsFeature & feature)17260 QgsFeature QgisApp::duplicateFeatures( QgsMapLayer *mlayer, const QgsFeature &feature )
17261 {
17262   if ( mlayer->type() != QgsMapLayerType::VectorLayer )
17263     return QgsFeature();
17264 
17265   QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mlayer );
17266 
17267   if ( !layer->isEditable() )
17268   {
17269     //should never happen because the action should be disabled
17270     QString msg = tr( "Cannot duplicate feature in not editable mode on layer %1" ).arg( layer->name() );
17271     visibleMessageBar()->pushMessage( msg, Qgis::MessageLevel::Warning );
17272     return QgsFeature();
17273   }
17274 
17275   QgsFeatureList featureList;
17276 
17277   if ( feature.isValid() )
17278   {
17279     featureList.append( feature );
17280   }
17281   else
17282   {
17283     featureList.append( layer->selectedFeatures() );
17284   }
17285 
17286   int featureCount = 0;
17287 
17288   QString childrenInfo;
17289 
17290   for ( const QgsFeature &f : featureList )
17291   {
17292     QgsVectorLayerUtils::QgsDuplicateFeatureContext duplicateFeatureContext;
17293 
17294     QgsVectorLayerUtils::duplicateFeature( layer, f, QgsProject::instance(), duplicateFeatureContext );
17295     featureCount += 1;
17296 
17297     const auto duplicatedFeatureContextLayers = duplicateFeatureContext.layers();
17298     for ( QgsVectorLayer *chl : duplicatedFeatureContextLayers )
17299     {
17300       childrenInfo += ( tr( "%1 children on layer %2 duplicated" ).arg( QLocale().toString( duplicateFeatureContext.duplicatedFeatures( chl ).size() ), chl->name() ) );
17301     }
17302   }
17303 
17304   visibleMessageBar()->pushMessage( tr( "%1 features on layer %2 duplicated\n%3" ).arg( QLocale().toString( featureCount ), layer->name(), childrenInfo ), Qgis::MessageLevel::Success );
17305 
17306   return QgsFeature();
17307 }
17308 
17309 
duplicateFeatureDigitized(QgsMapLayer * mlayer,const QgsFeature & feature)17310 QgsFeature QgisApp::duplicateFeatureDigitized( QgsMapLayer *mlayer, const QgsFeature &feature )
17311 {
17312   if ( mlayer->type() != QgsMapLayerType::VectorLayer )
17313     return QgsFeature();
17314 
17315   QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mlayer );
17316 
17317   if ( !layer->isEditable() )
17318   {
17319     //should never happen because the action should be disabled
17320     QString msg = tr( "Cannot duplicate feature in not editable mode on layer %1" ).arg( layer->name() );
17321     visibleMessageBar()->pushMessage( msg, Qgis::MessageLevel::Warning );
17322     return QgsFeature();
17323   }
17324 
17325   QgsMapToolDigitizeFeature *digitizeFeature = new QgsMapToolDigitizeFeature( mMapCanvas, mAdvancedDigitizingDockWidget, QgsMapToolCapture::CaptureNone );
17326   digitizeFeature->setLayer( layer );
17327 
17328   mMapCanvas->setMapTool( digitizeFeature );
17329   mMapCanvas->window()->raise();
17330   mMapCanvas->activateWindow();
17331   mMapCanvas->setFocus();
17332 
17333   QString msg = tr( "Digitize the duplicate on layer %1" ).arg( layer->name() );
17334   visibleMessageBar()->pushMessage( msg, Qgis::MessageLevel::Info );
17335 
17336   connect( digitizeFeature, static_cast<void ( QgsMapToolDigitizeFeature::* )( const QgsFeature & )>( &QgsMapToolDigitizeFeature::digitizingCompleted ), this, [this, layer, feature, digitizeFeature]( const QgsFeature & digitizedFeature )
17337   {
17338     QString msg = tr( "Duplicate digitized" );
17339     visibleMessageBar()->pushMessage( msg, Qgis::MessageLevel::Info );
17340 
17341     QgsVectorLayerUtils::QgsDuplicateFeatureContext duplicateFeatureContext;
17342 
17343     QgsFeature newFeature = feature;
17344     newFeature.setGeometry( digitizedFeature.geometry() );
17345     QgsVectorLayerUtils::duplicateFeature( layer, newFeature, QgsProject::instance(), duplicateFeatureContext );
17346 
17347     QString childrenInfo;
17348     const auto duplicateFeatureContextLayers = duplicateFeatureContext.layers();
17349     for ( QgsVectorLayer *chl : duplicateFeatureContextLayers )
17350     {
17351       childrenInfo += ( tr( "%1 children on layer %2 duplicated" ).arg( duplicateFeatureContext.duplicatedFeatures( chl ).size() ).arg( chl->name() ) );
17352     }
17353 
17354     visibleMessageBar()->pushMessage( tr( "Feature on layer %2 duplicated\n%3" ).arg( layer->name(), childrenInfo ), Qgis::MessageLevel::Success );
17355 
17356     mMapCanvas->unsetMapTool( digitizeFeature );
17357   }
17358          );
17359 
17360   connect( digitizeFeature, static_cast<void ( QgsMapToolDigitizeFeature::* )()>( &QgsMapToolDigitizeFeature::digitizingFinished ), this, [digitizeFeature]()
17361   {
17362     digitizeFeature->deleteLater();
17363   }
17364          );
17365 
17366   return QgsFeature();
17367 }
17368 
17369 
populateProjectStorageMenu(QMenu * menu,const bool saving)17370 void QgisApp::populateProjectStorageMenu( QMenu *menu, const bool saving )
17371 {
17372   menu->clear();
17373 
17374   if ( saving )
17375   {
17376     QAction *action = menu->addAction( tr( "Templates" ) + QChar( 0x2026 ) ); // 0x2026 = ellipsis character
17377     connect( action, &QAction::triggered, this, [ this ]
17378     {
17379       QgsSettings settings;
17380       QString templateDirName = settings.value( QStringLiteral( "qgis/projectTemplateDir" ),
17381           QString( QgsApplication::qgisSettingsDirPath() + "project_templates" ) ).toString();
17382 
17383       const QString originalFilename = QgsProject::instance()->fileName();
17384       QString templateName = QFileInfo( originalFilename ).baseName();
17385 
17386       if ( templateName.isEmpty() )
17387       {
17388         bool ok;
17389         templateName = QInputDialog::getText( this, tr( "Template Name" ),
17390                                               tr( "Name for the template" ), QLineEdit::Normal,
17391                                               QString(), &ok );
17392 
17393         if ( !ok )
17394           return;
17395         if ( templateName.isEmpty() )
17396         {
17397           messageBar()->pushInfo( tr( "Template not saved" ), tr( "The template can not have an empty name." ) );
17398         }
17399       }
17400       const QString filePath = templateDirName + QDir::separator() + templateName + QStringLiteral( ".qgz" );
17401       if ( QFileInfo::exists( filePath ) )
17402       {
17403         QMessageBox msgBox( this );
17404         msgBox.setWindowTitle( tr( "Overwrite Template" ) );
17405         msgBox.setText( tr( "The template %1 already exists, do you want to replace it?" ).arg( templateName ) );
17406         msgBox.addButton( tr( "Overwrite" ), QMessageBox::YesRole );
17407         auto cancelButton = msgBox.addButton( QMessageBox::Cancel );
17408         msgBox.setIcon( QMessageBox::Question );
17409         msgBox.exec();
17410         if ( msgBox.clickedButton() == cancelButton )
17411         {
17412           return;
17413         }
17414       }
17415 
17416       QgsProject::instance()->write( filePath );
17417       QgsProject::instance()->setFileName( originalFilename );
17418       messageBar()->pushInfo( tr( "Template saved" ), tr( "Template %1 was saved" ).arg( templateName ) );
17419 
17420     } );
17421   }
17422 
17423   const QList<QgsProjectStorageGuiProvider *> storageGuiProviders = QgsGui::projectStorageGuiRegistry()->projectStorages();
17424   for ( QgsProjectStorageGuiProvider *storageGuiProvider : storageGuiProviders )
17425   {
17426     QString name = storageGuiProvider->visibleName();
17427     if ( name.isEmpty() )
17428       continue;
17429     QAction *action = menu->addAction( name + QChar( 0x2026 ) ); // 0x2026 = ellipsis character
17430     if ( saving )
17431     {
17432       connect( action, &QAction::triggered, this, [this, storageGuiProvider]
17433       {
17434         QString uri = storageGuiProvider->showSaveGui();
17435         if ( !uri.isEmpty() )
17436         {
17437           saveProjectToProjectStorage( uri );
17438         }
17439       } );
17440     }
17441     else
17442     {
17443       connect( action, &QAction::triggered, this, [this, storageGuiProvider]
17444       {
17445         QString uri = storageGuiProvider->showLoadGui();
17446         if ( !uri.isEmpty() )
17447         {
17448           addProject( uri );
17449         }
17450       } );
17451     }
17452   }
17453 
17454   // support legacy API (before 3.10 core and gui related functions were mixed together in QgsProjectStorage)
17455   const QList<QgsProjectStorage *> storages = QgsApplication::projectStorageRegistry()->projectStorages();
17456   for ( QgsProjectStorage *storage : storages )
17457   {
17458     Q_NOWARN_DEPRECATED_PUSH
17459     QString name = storage->visibleName();
17460     Q_NOWARN_DEPRECATED_POP
17461     if ( name.isEmpty() )
17462       continue;
17463     QAction *action = menu->addAction( name + QChar( 0x2026 ) ); // 0x2026 = ellipsis character
17464     if ( saving )
17465     {
17466       connect( action, &QAction::triggered, this, [this, storage]
17467       {
17468         Q_NOWARN_DEPRECATED_PUSH
17469         QString uri = storage->showSaveGui();
17470         Q_NOWARN_DEPRECATED_POP
17471         if ( !uri.isEmpty() )
17472           saveProjectToProjectStorage( uri );
17473       } );
17474     }
17475     else
17476     {
17477       connect( action, &QAction::triggered, this, [this, storage]
17478       {
17479         Q_NOWARN_DEPRECATED_PUSH
17480         QString uri = storage->showLoadGui();
17481         Q_NOWARN_DEPRECATED_POP
17482         if ( !uri.isEmpty() )
17483           addProject( uri );
17484       } );
17485     }
17486   }
17487 }
17488 
saveProjectToProjectStorage(const QString & uri)17489 void QgisApp::saveProjectToProjectStorage( const QString &uri )
17490 {
17491   QgsProject::instance()->setFileName( uri );
17492   if ( QgsProject::instance()->write() )
17493   {
17494     setTitleBarText_( *this ); // update title bar
17495     mStatusBar->showMessage( tr( "Saved project to: %1" ).arg( uri ), 5000 );
17496     // add this to the list of recently used project files
17497     saveRecentProjectPath();
17498     mProjectLastModified = QgsProject::instance()->lastModified();
17499   }
17500   else
17501   {
17502     QMessageBox msgbox;
17503 
17504     msgbox.setWindowTitle( tr( "Save Project" ) );
17505     msgbox.setText( QgsProject::instance()->error() );
17506     msgbox.setIcon( QMessageBox::Icon::Critical );
17507     msgbox.addButton( QMessageBox::Cancel );
17508     msgbox.addButton( QMessageBox::Save );
17509     msgbox.setButtonText( QMessageBox::Save, tr( "Save as Local File" ) );
17510     msgbox.setDefaultButton( QMessageBox::Cancel );
17511     msgbox.exec();
17512 
17513     if ( msgbox.result() == QMessageBox::Save )
17514     {
17515       fileSaveAs();
17516     }
17517   }
17518 }
17519 
triggerCrashHandler()17520 void QgisApp::triggerCrashHandler()
17521 {
17522 #ifdef Q_OS_WIN
17523   RaiseException( 0x12345678, 0, 0, nullptr );
17524 #endif
17525 }
17526 
addTabifiedDockWidget(Qt::DockWidgetArea area,QDockWidget * dockWidget,const QStringList & tabifyWith,bool raiseTab)17527 void QgisApp::addTabifiedDockWidget( Qt::DockWidgetArea area, QDockWidget *dockWidget, const QStringList &tabifyWith, bool raiseTab )
17528 {
17529   QList< QDockWidget * > dockWidgetsInArea;
17530   const auto dockWidgets = findChildren< QDockWidget * >();
17531   for ( QDockWidget *w : dockWidgets )
17532   {
17533     if ( w->isVisible() && dockWidgetArea( w ) == area )
17534     {
17535       dockWidgetsInArea << w;
17536     }
17537   }
17538 
17539   addDockWidget( area, dockWidget );  // First add the dock widget, then attempt to tabify
17540   if ( dockWidgetsInArea.length() > 0 )
17541   {
17542     // Get the base dock widget that we'll use to tabify our new dockWidget
17543     QDockWidget *tabifyWithDockWidget = nullptr;
17544     if ( !tabifyWith.isEmpty() )
17545     {
17546       // Iterate the list of object names looking for a
17547       // dock widget to tabify the new one on top of it
17548       bool objectNameFound = false;
17549       for ( int i = 0; i < tabifyWith.size(); i++ )
17550       {
17551         for ( QDockWidget *cw : dockWidgetsInArea )
17552         {
17553           if ( cw->objectName() == tabifyWith.at( i ) )
17554           {
17555             tabifyWithDockWidget = cw;
17556             objectNameFound = true;  // Also exit the outer for loop
17557             break;
17558           }
17559         }
17560         if ( objectNameFound )
17561         {
17562           break;
17563         }
17564       }
17565     }
17566     if ( !tabifyWithDockWidget )
17567     {
17568       tabifyWithDockWidget = dockWidgetsInArea.at( 0 );  // Last resort
17569     }
17570 
17571     QTabBar *existentTabBar = nullptr;
17572     int currentIndex = -1;
17573     if ( !raiseTab && dockWidgetsInArea.length() > 1 )
17574     {
17575       // Chances are we've already got a tabBar, if so, get
17576       // currentIndex to restore status after inserting our new tab
17577       const QList<QTabBar *> tabBars = findChildren<QTabBar *>( QString(), Qt::FindDirectChildrenOnly );
17578       bool tabBarFound = false;
17579       for ( QTabBar *tabBar : tabBars )
17580       {
17581         for ( int i = 0; i < tabBar->count(); i++ )
17582         {
17583           if ( tabBar->tabText( i ) == tabifyWithDockWidget->windowTitle() )
17584           {
17585             existentTabBar = tabBar;
17586             currentIndex = tabBar->currentIndex();
17587             tabBarFound = true;
17588             break;
17589           }
17590         }
17591         if ( tabBarFound )
17592         {
17593           break;
17594         }
17595       }
17596     }
17597 
17598     // Now we can put the new dockWidget on top of tabifyWith
17599     tabifyDockWidget( tabifyWithDockWidget, dockWidget );
17600 
17601     // Should we restore dock widgets status?
17602     if ( !raiseTab )
17603     {
17604       if ( existentTabBar )
17605       {
17606         existentTabBar->setCurrentIndex( currentIndex );
17607       }
17608       else
17609       {
17610         tabifyWithDockWidget->raise();  // Single base dock widget, we can just raise it
17611       }
17612     }
17613   }
17614 }
17615 
createAttributeEditorContext()17616 QgsAttributeEditorContext QgisApp::createAttributeEditorContext()
17617 {
17618   QgsAttributeEditorContext context;
17619   context.setVectorLayerTools( vectorLayerTools() );
17620   context.setMapCanvas( mapCanvas() );
17621   context.setCadDockWidget( cadDockWidget() );
17622   context.setMainMessageBar( messageBar() );
17623   return context;
17624 }
17625 
showEvent(QShowEvent * event)17626 void QgisApp::showEvent( QShowEvent *event )
17627 {
17628   QMainWindow::showEvent( event );
17629   // because of Qt regression: https://bugreports.qt.io/browse/QTBUG-89034
17630   // we have to wait till dialog is first shown to try to restore dock geometry or it's not correctly restored
17631   static std::once_flag firstShow;
17632   std::call_once( firstShow, [this]
17633   {
17634     QgsSettings settings;
17635     if ( !restoreState( settings.value( QStringLiteral( "UI/state" ), QByteArray::fromRawData( reinterpret_cast< const char * >( defaultUIstate ), sizeof defaultUIstate ) ).toByteArray() ) )
17636     {
17637       QgsDebugMsg( QStringLiteral( "restore of UI state failed" ) );
17638     }
17639   } );
17640 }
17641