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 ¤tTitle )
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 ¤tPage, 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 ¤tPage )
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