mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-11 00:04:27 -04:00
12828 lines
445 KiB
C++
12828 lines
445 KiB
C++
/***************************************************************************
|
|
qgisapp.cpp - description
|
|
-------------------
|
|
|
|
begin : Sat Jun 22 2002
|
|
copyright : (C) 2002 by Gary E.Sherman
|
|
email : sherman at mrcc.com
|
|
Romans 3:23=>Romans 6:23=>Romans 10:9,10=>Romans 12
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include <QAction>
|
|
#include <QApplication>
|
|
#include <QBitmap>
|
|
#include <QCheckBox>
|
|
#include <QClipboard>
|
|
#include <QColor>
|
|
#include <QCursor>
|
|
#include <QDesktopServices>
|
|
#include <QDesktopWidget>
|
|
#include <QDialog>
|
|
#include <QDialogButtonBox>
|
|
#include <QDir>
|
|
#include <QEvent>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QImageWriter>
|
|
#include <QInputDialog>
|
|
#include <QKeyEvent>
|
|
#include <QLabel>
|
|
#include <QLibrary>
|
|
#include <QMenu>
|
|
#include <QMenuBar>
|
|
#include <QMessageBox>
|
|
#include <QPainter>
|
|
#include <QPictureIO>
|
|
#include <QPixmap>
|
|
#include <QPoint>
|
|
#include <QPrinter>
|
|
#include <QProcess>
|
|
#include <QProgressBar>
|
|
#include <QProgressDialog>
|
|
#include <QRegExp>
|
|
#include <QRegExpValidator>
|
|
#include <QScreen>
|
|
#include <QShortcut>
|
|
#include <QSpinBox>
|
|
#include <QSplashScreen>
|
|
#ifndef QT_NO_SSL
|
|
#include <QSslConfiguration>
|
|
#endif
|
|
#include <QStatusBar>
|
|
#include <QStringList>
|
|
#include <QSystemTrayIcon>
|
|
#include <QTcpSocket>
|
|
#include <QTextStream>
|
|
#include <QtGlobal>
|
|
#include <QThread>
|
|
#include <QTimer>
|
|
#include <QToolButton>
|
|
#include <QUuid>
|
|
#include <QVBoxLayout>
|
|
#include <QWhatsThis>
|
|
#include <QWidgetAction>
|
|
|
|
#include "qgssettings.h"
|
|
#include "qgsnetworkaccessmanager.h"
|
|
#include "qgsapplication.h"
|
|
#include "qgscomposition.h"
|
|
#include "qgslayerstylingwidget.h"
|
|
#include "qgstaskmanager.h"
|
|
#include "qgsziputils.h"
|
|
#include "qgsbrowsermodel.h"
|
|
|
|
#ifdef HAVE_3D
|
|
#include "qgsabstract3drenderer.h"
|
|
#include "qgs3dmapcanvasdockwidget.h"
|
|
#include "qgs3drendererregistry.h"
|
|
#include "qgs3dmapsettings.h"
|
|
#include "qgsflatterraingenerator.h"
|
|
#include "qgsvectorlayer3drenderer.h"
|
|
#endif
|
|
|
|
#include <QNetworkReply>
|
|
#include <QNetworkProxy>
|
|
#include <QAuthenticator>
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <QWinTaskbarButton>
|
|
#include <QWinTaskbarProgress>
|
|
#endif
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <QtWinExtras/QWinJumpList>
|
|
#include <QtWinExtras/QWinJumpListItem>
|
|
#include <QtWinExtras/QWinJumpListCategory>
|
|
#endif
|
|
|
|
Q_GUI_EXPORT extern int qt_defaultDpiX();
|
|
|
|
//
|
|
// Mac OS X Includes
|
|
// Must include before GEOS 3 due to unqualified use of 'Point'
|
|
//
|
|
#ifdef Q_OS_MACX
|
|
#include <ApplicationServices/ApplicationServices.h>
|
|
#include "qgsmacnative.h"
|
|
|
|
// check macro breaks QItemDelegate
|
|
#ifdef check
|
|
#undef check
|
|
#endif
|
|
#endif
|
|
|
|
//
|
|
// QGIS Specific Includes
|
|
//
|
|
|
|
#include "qgscrashhandler.h"
|
|
|
|
#include "qgisapp.h"
|
|
#include "qgisappinterface.h"
|
|
#include "qgisappstylesheet.h"
|
|
#include "qgis.h"
|
|
#include "qgisplugin.h"
|
|
#include "qgsabout.h"
|
|
#include "qgsalignrasterdialog.h"
|
|
#include "qgsappbrowserproviders.h"
|
|
#include "qgsapplayertreeviewmenuprovider.h"
|
|
#include "qgsapplication.h"
|
|
#include "qgsactionmanager.h"
|
|
#include "qgsannotationmanager.h"
|
|
#include "qgsannotationregistry.h"
|
|
#include "qgsattributetabledialog.h"
|
|
#include "qgsattributedialog.h"
|
|
#include "qgsauthmanager.h"
|
|
#include "qgsauthguiutils.h"
|
|
#ifndef QT_NO_SSL
|
|
#include "qgsauthcertutils.h"
|
|
#include "qgsauthsslerrorsdialog.h"
|
|
#endif
|
|
#include "qgsbookmarks.h"
|
|
#include "qgsbrowserdockwidget.h"
|
|
#include "qgsadvanceddigitizingdockwidget.h"
|
|
#include "qgsclipboard.h"
|
|
#include "qgscomposer.h"
|
|
#include "qgscomposermanager.h"
|
|
#include "qgscomposerview.h"
|
|
#include "qgsconfigureshortcutsdialog.h"
|
|
#include "qgscoordinatetransform.h"
|
|
#include "qgscoordinateutils.h"
|
|
#include "qgscredentialdialog.h"
|
|
#include "qgscursors.h"
|
|
#include "qgscustomdrophandler.h"
|
|
#include "qgscustomization.h"
|
|
#include "qgscustomlayerorderwidget.h"
|
|
#include "qgscustomprojectiondialog.h"
|
|
#include "qgsdataitemproviderregistry.h"
|
|
#include "qgsdatasourceuri.h"
|
|
#include "qgsdatumtransformdialog.h"
|
|
#include "qgsdoublespinbox.h"
|
|
#include "qgsdockwidget.h"
|
|
#include "qgsdxfexport.h"
|
|
#include "qgsdxfexportdialog.h"
|
|
#include "qgsdwgimportdialog.h"
|
|
#include "qgsdecorationcopyright.h"
|
|
#include "qgsdecorationnortharrow.h"
|
|
#include "qgsdecorationscalebar.h"
|
|
#include "qgsdecorationgrid.h"
|
|
#include "qgsdecorationlayoutextent.h"
|
|
#include "qgsencodingfiledialog.h"
|
|
#include "qgserror.h"
|
|
#include "qgserrordialog.h"
|
|
#include "qgsexception.h"
|
|
#include "qgsexpressionselectiondialog.h"
|
|
#include "qgsfeature.h"
|
|
#include "qgsfieldcalculator.h"
|
|
#include "qgsfieldformatter.h"
|
|
#include "qgsfieldformatterregistry.h"
|
|
#include "qgsformannotation.h"
|
|
#include "qgsguiutils.h"
|
|
#include "qgshtmlannotation.h"
|
|
#include "qgsprojectionselectiondialog.h"
|
|
#include "qgsgpsinformationwidget.h"
|
|
#include "qgsguivectorlayertools.h"
|
|
#include "qgslabelingwidget.h"
|
|
#include "qgsdiagramproperties.h"
|
|
#include "qgslayerdefinition.h"
|
|
#include "qgslayertree.h"
|
|
#include "qgslayertreemapcanvasbridge.h"
|
|
#include "qgslayertreemodel.h"
|
|
#include "qgslayertreemodellegendnode.h"
|
|
#include "qgslayertreeregistrybridge.h"
|
|
#include "qgslayertreeutils.h"
|
|
#include "qgslayertreeview.h"
|
|
#include "qgslayertreeviewdefaultactions.h"
|
|
#include "qgslayoutdesignerdialog.h"
|
|
#include "qgslayoutmanager.h"
|
|
#include "qgslocatorwidget.h"
|
|
#include "qgslocator.h"
|
|
#include "qgsinbuiltlocatorfilters.h"
|
|
#include "qgslogger.h"
|
|
#include "qgsmapcanvas.h"
|
|
#include "qgsmapcanvasdockwidget.h"
|
|
#include "qgsmapcanvassnappingutils.h"
|
|
#include "qgsmapcanvastracer.h"
|
|
#include "qgsmaplayer.h"
|
|
#include "qgsmaplayerstyleguiutils.h"
|
|
#include "qgsmapoverviewcanvas.h"
|
|
#include "qgsmapsettings.h"
|
|
#include "qgsmaptip.h"
|
|
#include "qgsmenuheader.h"
|
|
#include "qgsmergeattributesdialog.h"
|
|
#include "qgsmessageviewer.h"
|
|
#include "qgsmessagebar.h"
|
|
#include "qgsmessagebaritem.h"
|
|
#include "qgsmemoryproviderutils.h"
|
|
#include "qgsmimedatautils.h"
|
|
#include "qgsmessagelog.h"
|
|
#include "qgsmultibandcolorrenderer.h"
|
|
#include "qgsnative.h"
|
|
#include "qgsnewvectorlayerdialog.h"
|
|
#include "qgsnewmemorylayerdialog.h"
|
|
#include "qgsoptions.h"
|
|
#include "qgspluginlayer.h"
|
|
#include "qgspluginlayerregistry.h"
|
|
#include "qgspluginmanager.h"
|
|
#include "qgspluginregistry.h"
|
|
#include "qgspointxy.h"
|
|
#include "qgsruntimeprofiler.h"
|
|
#include "qgshandlebadlayers.h"
|
|
#include "qgsproject.h"
|
|
#include "qgsprojectlayergroupdialog.h"
|
|
#include "qgsprojectproperties.h"
|
|
#include "qgsproviderregistry.h"
|
|
#include "qgspythonrunner.h"
|
|
#include "qgsquerybuilder.h"
|
|
#include "qgsrastercalcdialog.h"
|
|
#include "qgsrasterfilewriter.h"
|
|
#include "qgsrasterfilewritertask.h"
|
|
#include "qgsrasteriterator.h"
|
|
#include "qgsrasterlayer.h"
|
|
#include "qgsrasterlayerproperties.h"
|
|
#include "qgsrasternuller.h"
|
|
#include "qgsbrightnesscontrastfilter.h"
|
|
#include "qgsrasterrenderer.h"
|
|
#include "qgsrasterlayersaveasdialog.h"
|
|
#include "qgsrasterprojector.h"
|
|
#include "qgsreadwritecontext.h"
|
|
#include "qgsrectangle.h"
|
|
#include "qgsscalevisibilitydialog.h"
|
|
#include "qgsgroupwmsdatadialog.h"
|
|
#include "qgsselectbyformdialog.h"
|
|
#include "qgsshortcutsmanager.h"
|
|
#include "qgssinglebandgrayrenderer.h"
|
|
#include "qgssnappingwidget.h"
|
|
#include "qgsstatisticalsummarydockwidget.h"
|
|
#include "qgsstatusbar.h"
|
|
#include "qgsstatusbarcoordinateswidget.h"
|
|
#include "qgsstatusbarmagnifierwidget.h"
|
|
#include "qgsstatusbarscalewidget.h"
|
|
#include "qgsstyle.h"
|
|
#include "qgssvgannotation.h"
|
|
#include "qgstaskmanager.h"
|
|
#include "qgstaskmanagerwidget.h"
|
|
#include "qgssymbolselectordialog.h"
|
|
#include "qgstextannotation.h"
|
|
#include "qgsundowidget.h"
|
|
#include "qgsuserinputdockwidget.h"
|
|
#include "qgsvectordataprovider.h"
|
|
#include "qgsvectorfilewriter.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsvectorlayerproperties.h"
|
|
#include "qgsmapthemes.h"
|
|
#include "qgsmessagelogviewer.h"
|
|
#include "qgsdataitem.h"
|
|
#include "qgsmaplayeractionregistry.h"
|
|
#include "qgswelcomepage.h"
|
|
#include "qgsversioninfo.h"
|
|
#include "qgslegendfilterbutton.h"
|
|
#include "qgsvirtuallayerdefinition.h"
|
|
#include "qgsvirtuallayerdefinitionutils.h"
|
|
#include "qgstransaction.h"
|
|
#include "qgstransactiongroup.h"
|
|
#include "qgsvectorlayerjoininfo.h"
|
|
#include "qgsvectorlayerutils.h"
|
|
#include "qgshelp.h"
|
|
#include "qgsvectorfilewritertask.h"
|
|
#include "qgsmapsavedialog.h"
|
|
#include "qgsmaprenderertask.h"
|
|
#include "qgsmapdecoration.h"
|
|
#include "qgsnewnamedialog.h"
|
|
#include "qgsgui.h"
|
|
#include "qgsdatasourcemanagerdialog.h"
|
|
|
|
#include "qgsuserprofilemanager.h"
|
|
#include "qgsuserprofile.h"
|
|
|
|
#include "qgssublayersdialog.h"
|
|
#include "ogr/qgsvectorlayersaveasdialog.h"
|
|
|
|
#include "qgsosmdownloaddialog.h"
|
|
#include "qgsosmimportdialog.h"
|
|
#include "qgsosmexportdialog.h"
|
|
|
|
#ifdef ENABLE_MODELTEST
|
|
#include "modeltest.h"
|
|
#endif
|
|
|
|
//
|
|
// GDAL/OGR includes
|
|
//
|
|
#include <ogr_api.h>
|
|
#include <gdal_version.h>
|
|
#include <proj_api.h>
|
|
|
|
//
|
|
// Other includes
|
|
//
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <functional>
|
|
#include <iomanip>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
//
|
|
// Map tools
|
|
//
|
|
#include "qgsmaptooladdfeature.h"
|
|
#include "qgsmaptooladdpart.h"
|
|
#include "qgsmaptooladdring.h"
|
|
#include "qgsmaptoolfillring.h"
|
|
#include "qgsmaptoolannotation.h"
|
|
#include "qgsmaptoolcircularstringcurvepoint.h"
|
|
#include "qgsmaptoolcircularstringradius.h"
|
|
#include "qgsmaptoolcircle2points.h"
|
|
#include "qgsmaptoolcircle3points.h"
|
|
#include "qgsmaptoolcircle3tangents.h"
|
|
#include "qgsmaptoolcircle2tangentspoint.h"
|
|
#include "qgsmaptoolcirclecenterpoint.h"
|
|
#include "qgsmaptoolellipsecenter2points.h"
|
|
#include "qgsmaptoolellipsecenterpoint.h"
|
|
#include "qgsmaptoolellipseextent.h"
|
|
#include "qgsmaptoolellipsefoci.h"
|
|
#include "qgsmaptoolrectanglecenter.h"
|
|
#include "qgsmaptoolrectangleextent.h"
|
|
#include "qgsmaptoolrectangle3points.h"
|
|
#include "qgsmaptoolregularpolygon2points.h"
|
|
#include "qgsmaptoolregularpolygoncenterpoint.h"
|
|
#include "qgsmaptoolregularpolygoncentercorner.h"
|
|
#include "qgsmaptooldeletering.h"
|
|
#include "qgsmaptooldeletepart.h"
|
|
#include "qgsmaptoolfeatureaction.h"
|
|
#include "qgsmaptoolformannotation.h"
|
|
#include "qgsmaptoolhtmlannotation.h"
|
|
#include "qgsmaptoolidentifyaction.h"
|
|
#include "qgsmaptoolmeasureangle.h"
|
|
#include "qgsmaptoolmovefeature.h"
|
|
#include "qgsmaptoolrotatefeature.h"
|
|
#include "qgsmaptooloffsetcurve.h"
|
|
#include "qgsmaptooloffsetpointsymbol.h"
|
|
#include "qgsmaptoolpan.h"
|
|
#include "qgsmaptoolselect.h"
|
|
#include "qgsmaptoolselectrectangle.h"
|
|
#include "qgsmaptoolselectfreehand.h"
|
|
#include "qgsmaptoolselectpolygon.h"
|
|
#include "qgsmaptoolselectradius.h"
|
|
#include "qgsmaptoolsvgannotation.h"
|
|
#include "qgsmaptoolreshape.h"
|
|
#include "qgsmaptoolrotatepointsymbols.h"
|
|
#include "qgsmaptoolsplitfeatures.h"
|
|
#include "qgsmaptoolsplitparts.h"
|
|
#include "qgsmaptooltextannotation.h"
|
|
#include "qgsmaptoolzoom.h"
|
|
#include "qgsmaptoolsimplify.h"
|
|
#include "qgsmeasuretool.h"
|
|
#include "qgsmaptoolpinlabels.h"
|
|
#include "qgsmaptoolshowhidelabels.h"
|
|
#include "qgsmaptoolmovelabel.h"
|
|
#include "qgsmaptoolrotatelabel.h"
|
|
#include "qgsmaptoolchangelabelproperties.h"
|
|
|
|
#include "nodetool/qgsnodetool.h"
|
|
|
|
// Editor widgets
|
|
#include "qgseditorwidgetregistry.h"
|
|
//
|
|
// Conditional Includes
|
|
//
|
|
#ifdef HAVE_PGCONFIG
|
|
#undef PACKAGE_BUGREPORT
|
|
#undef PACKAGE_NAME
|
|
#undef PACKAGE_STRING
|
|
#undef PACKAGE_TARNAME
|
|
#undef PACKAGE_VERSION
|
|
#include <pg_config.h>
|
|
#else
|
|
#define PG_VERSION "unknown"
|
|
#endif
|
|
|
|
#include <sqlite3.h>
|
|
|
|
extern "C"
|
|
{
|
|
#include <spatialite.h>
|
|
}
|
|
#include "qgsnewspatialitelayerdialog.h"
|
|
#include "qgsnewgeopackagelayerdialog.h"
|
|
|
|
#ifdef WITH_BINDINGS
|
|
#include "qgspythonutils.h"
|
|
#endif
|
|
|
|
#ifndef Q_OS_WIN
|
|
#include <dlfcn.h>
|
|
#else
|
|
#include <windows.h>
|
|
#include <DbgHelp.h>
|
|
#endif
|
|
|
|
class QTreeWidgetItem;
|
|
class QgsUserProfileManager;
|
|
class QgsUserProfile;
|
|
|
|
/** Set the application title bar text
|
|
|
|
If the current project title is null
|
|
if the project file is null then
|
|
set title text to just application name
|
|
else
|
|
set set title text to the project file name
|
|
else
|
|
set the title text to project title
|
|
*/
|
|
static void setTitleBarText_( QWidget &qgisApp )
|
|
{
|
|
QString caption;
|
|
if ( QgsProject::instance()->title().isEmpty() )
|
|
{
|
|
if ( QgsProject::instance()->fileName().isEmpty() )
|
|
{
|
|
// no project title nor file name, so just leave caption with
|
|
// application name and version
|
|
}
|
|
else
|
|
{
|
|
QFileInfo projectFileInfo( QgsProject::instance()->fileName() );
|
|
caption = projectFileInfo.completeBaseName() + " - ";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
caption = QgsProject::instance()->title() + " - ";
|
|
}
|
|
|
|
caption += QgisApp::tr( "QGIS" );
|
|
|
|
if ( Qgis::QGIS_VERSION.endsWith( QLatin1String( "Master" ) ) )
|
|
{
|
|
caption += QStringLiteral( " %1" ).arg( Qgis::QGIS_DEV_VERSION );
|
|
}
|
|
|
|
qgisApp.setWindowTitle( caption );
|
|
}
|
|
|
|
/**
|
|
Creator function for output viewer
|
|
*/
|
|
static QgsMessageOutput *messageOutputViewer_()
|
|
{
|
|
if ( QThread::currentThread() == qApp->thread() )
|
|
return new QgsMessageViewer( QgisApp::instance() );
|
|
else
|
|
return new QgsMessageOutputConsole();
|
|
}
|
|
|
|
static void customSrsValidation_( QgsCoordinateReferenceSystem &srs )
|
|
{
|
|
QgisApp::instance()->emitCustomCrsValidation( srs );
|
|
}
|
|
|
|
void QgisApp::emitCustomCrsValidation( QgsCoordinateReferenceSystem &srs )
|
|
{
|
|
emit customCrsValidation( srs );
|
|
}
|
|
|
|
void QgisApp::layerTreeViewDoubleClicked( const QModelIndex &index )
|
|
{
|
|
Q_UNUSED( index )
|
|
QgsSettings settings;
|
|
switch ( settings.value( QStringLiteral( "qgis/legendDoubleClickAction" ), 0 ).toInt() )
|
|
{
|
|
case 0:
|
|
{
|
|
//show properties
|
|
if ( mLayerTreeView )
|
|
{
|
|
// if it's a legend node, open symbol editor directly
|
|
if ( QgsSymbolLegendNode *node = dynamic_cast<QgsSymbolLegendNode *>( mLayerTreeView->currentLegendNode() ) )
|
|
{
|
|
const QgsSymbol *originalSymbol = node->symbol();
|
|
if ( !originalSymbol )
|
|
return;
|
|
|
|
std::unique_ptr< QgsSymbol > symbol( originalSymbol->clone() );
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( node->layerNode()->layer() );
|
|
QgsSymbolSelectorDialog dlg( symbol.get(), QgsStyle::defaultStyle(), vlayer, this );
|
|
QgsSymbolWidgetContext context;
|
|
context.setMapCanvas( mMapCanvas );
|
|
dlg.setContext( context );
|
|
if ( dlg.exec() )
|
|
{
|
|
node->setSymbol( symbol.release() );
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
QgisApp::instance()->layerProperties();
|
|
break;
|
|
}
|
|
case 1:
|
|
QgisApp::instance()->attributeTable();
|
|
break;
|
|
case 2:
|
|
mapStyleDock( true );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void QgisApp::activeLayerChanged( QgsMapLayer *layer )
|
|
{
|
|
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
|
|
canvas->setCurrentLayer( layer );
|
|
|
|
if ( mUndoWidget )
|
|
{
|
|
if ( layer )
|
|
{
|
|
mUndoWidget->setUndoStack( layer->undoStack() );
|
|
}
|
|
else
|
|
{
|
|
mUndoWidget->destroyStack();
|
|
}
|
|
updateUndoActions();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function contains forced validation of CRS used in QGIS.
|
|
* There are 3 options depending on the settings:
|
|
* - ask for CRS using projection selecter
|
|
* - use project's CRS
|
|
* - use predefined global CRS
|
|
*/
|
|
void QgisApp::validateCrs( QgsCoordinateReferenceSystem &srs )
|
|
{
|
|
static QString sAuthId = QString();
|
|
QgsSettings mySettings;
|
|
QString myDefaultProjectionOption = mySettings.value( QStringLiteral( "Projections/defaultBehavior" ), "prompt" ).toString();
|
|
if ( myDefaultProjectionOption == QLatin1String( "prompt" ) )
|
|
{
|
|
// @note this class is not a descendent of QWidget so we can't pass
|
|
// it in the ctor of the layer projection selector
|
|
|
|
QgsProjectionSelectionDialog *mySelector = new QgsProjectionSelectionDialog();
|
|
mySelector->setMessage( srs.validationHint() ); //shows a generic message, if not specified
|
|
if ( sAuthId.isNull() )
|
|
sAuthId = QgsProject::instance()->crs().authid();
|
|
|
|
QgsCoordinateReferenceSystem defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( sAuthId );
|
|
if ( defaultCrs.isValid() )
|
|
{
|
|
mySelector->setCrs( defaultCrs );
|
|
}
|
|
|
|
bool waiting = QApplication::overrideCursor() && QApplication::overrideCursor()->shape() == Qt::WaitCursor;
|
|
if ( waiting )
|
|
QApplication::setOverrideCursor( Qt::ArrowCursor );
|
|
|
|
if ( mySelector->exec() )
|
|
{
|
|
QgsDebugMsg( "Layer srs set from dialog: " + QString::number( mySelector->crs().srsid() ) );
|
|
srs = mySelector->crs();
|
|
sAuthId = srs.authid();
|
|
}
|
|
|
|
if ( waiting )
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
delete mySelector;
|
|
}
|
|
else if ( myDefaultProjectionOption == QLatin1String( "useProject" ) )
|
|
{
|
|
// XXX TODO: Change project to store selected CS as 'projectCRS' not 'selectedWkt'
|
|
sAuthId = QgsProject::instance()->crs().authid();
|
|
srs.createFromOgcWmsCrs( sAuthId );
|
|
QgsDebugMsg( "Layer srs set from project: " + sAuthId );
|
|
messageBar()->pushMessage( tr( "CRS was undefined" ), tr( "defaulting to project CRS %1 - %2" ).arg( sAuthId, srs.description() ), QgsMessageBar::WARNING, messageTimeout() );
|
|
}
|
|
else ///Projections/defaultBehavior==useGlobal
|
|
{
|
|
sAuthId = mySettings.value( QStringLiteral( "Projections/layerDefaultCrs" ), GEO_EPSG_CRS_AUTHID ).toString();
|
|
srs.createFromOgcWmsCrs( sAuthId );
|
|
QgsDebugMsg( "Layer srs set from default: " + sAuthId );
|
|
messageBar()->pushMessage( tr( "CRS was undefined" ), tr( "defaulting to CRS %1 - %2" ).arg( sAuthId, srs.description() ), QgsMessageBar::WARNING, messageTimeout() );
|
|
}
|
|
}
|
|
|
|
static bool cmpByText_( QAction *a, QAction *b )
|
|
{
|
|
return QString::localeAwareCompare( a->text(), b->text() ) < 0;
|
|
}
|
|
|
|
|
|
QgisApp *QgisApp::sInstance = nullptr;
|
|
|
|
// constructor starts here
|
|
QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCheck, const QString &rootProfileLocation, const QString &activeProfile, QWidget *parent, Qt::WindowFlags fl )
|
|
: QMainWindow( parent, fl )
|
|
, mSplash( splash )
|
|
{
|
|
if ( sInstance )
|
|
{
|
|
QMessageBox::critical(
|
|
this,
|
|
tr( "Multiple Instances of QgisApp" ),
|
|
tr( "Multiple instances of QGIS application object detected.\nPlease contact the developers.\n" ) );
|
|
abort();
|
|
}
|
|
|
|
sInstance = this;
|
|
QgsRuntimeProfiler *profiler = QgsApplication::profiler();
|
|
|
|
startProfile( QStringLiteral( "User profile manager" ) );
|
|
mUserProfileManager = new QgsUserProfileManager( QString(), this );
|
|
mUserProfileManager->setRootLocation( rootProfileLocation );
|
|
mUserProfileManager->setActiveUserProfile( activeProfile );
|
|
mUserProfileManager->setNewProfileNotificationEnabled( true );
|
|
connect( mUserProfileManager, &QgsUserProfileManager::profilesChanged, this, &QgisApp::refreshProfileMenu );
|
|
endProfile();
|
|
|
|
// load GUI: actions, menus, toolbars
|
|
profiler->beginGroup( QStringLiteral( "qgisapp" ) );
|
|
profiler->beginGroup( QStringLiteral( "startup" ) );
|
|
startProfile( QStringLiteral( "Setting up UI" ) );
|
|
setupUi( this );
|
|
endProfile();
|
|
|
|
#if QT_VERSION >= 0x050600
|
|
setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
|
|
#endif
|
|
|
|
//////////
|
|
|
|
startProfile( QStringLiteral( "Checking database" ) );
|
|
mSplash->showMessage( tr( "Checking database" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
qApp->processEvents();
|
|
// Do this early on before anyone else opens it and prevents us copying it
|
|
QString dbError;
|
|
if ( !QgsApplication::createDatabase( &dbError ) )
|
|
{
|
|
QMessageBox::critical( this, tr( "Private qgis.db" ), dbError );
|
|
}
|
|
endProfile();
|
|
|
|
mTray = new QSystemTrayIcon();
|
|
mTray->setIcon( QIcon( QgsApplication::appIconPath() ) );
|
|
mTray->hide();
|
|
|
|
startProfile( QStringLiteral( "Initializing authentication" ) );
|
|
mSplash->showMessage( tr( "Initializing authentication" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
qApp->processEvents();
|
|
QgsAuthManager::instance()->init( QgsApplication::pluginPath() );
|
|
if ( !QgsAuthManager::instance()->isDisabled() )
|
|
{
|
|
masterPasswordSetup();
|
|
}
|
|
endProfile();
|
|
|
|
// Setup QgsNetworkAccessManager (this needs to happen after authentication, for proxy settings)
|
|
namSetup();
|
|
|
|
// Create the themes folder for the user
|
|
startProfile( QStringLiteral( "Creating theme folder" ) );
|
|
QgsApplication::createThemeFolder();
|
|
endProfile();
|
|
|
|
mSplash->showMessage( tr( "Reading settings" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
qApp->processEvents();
|
|
|
|
mSplash->showMessage( tr( "Setting up the GUI" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
qApp->processEvents();
|
|
|
|
QgsSettings settings;
|
|
|
|
startProfile( QStringLiteral( "Building style sheet" ) );
|
|
// set up stylesheet builder and apply saved or default style options
|
|
mStyleSheetBuilder = new QgisAppStyleSheet( this );
|
|
connect( mStyleSheetBuilder, &QgisAppStyleSheet::appStyleSheetChanged,
|
|
this, &QgisApp::setAppStyleSheet );
|
|
mStyleSheetBuilder->buildStyleSheet( mStyleSheetBuilder->defaultOptions() );
|
|
endProfile();
|
|
|
|
QWidget *centralWidget = this->centralWidget();
|
|
QGridLayout *centralLayout = new QGridLayout( centralWidget );
|
|
centralWidget->setLayout( centralLayout );
|
|
centralLayout->setContentsMargins( 0, 0, 0, 0 );
|
|
|
|
// "theMapCanvas" used to find this canonical instance later
|
|
startProfile( QStringLiteral( "Creating map canvas" ) );
|
|
mMapCanvas = new QgsMapCanvas( centralWidget );
|
|
mMapCanvas->setObjectName( QStringLiteral( "theMapCanvas" ) );
|
|
connect( mMapCanvas, &QgsMapCanvas::messageEmitted, this, &QgisApp::displayMessage );
|
|
mMapCanvas->setWhatsThis( tr( "Map canvas. This is where raster and vector "
|
|
"layers are displayed when added to the map" ) );
|
|
mMapCanvas->setPreviewJobsEnabled( true );
|
|
|
|
// set canvas color right away
|
|
int myRed = settings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
|
|
int myGreen = settings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
|
|
int myBlue = settings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
|
|
mMapCanvas->setCanvasColor( QColor( myRed, myGreen, myBlue ) );
|
|
endProfile();
|
|
|
|
// what type of project to auto-open
|
|
mProjOpen = settings.value( QStringLiteral( "qgis/projOpenAtLaunch" ), 0 ).toInt();
|
|
|
|
|
|
startProfile( QStringLiteral( "Welcome page" ) );
|
|
mWelcomePage = new QgsWelcomePage( skipVersionCheck );
|
|
endProfile();
|
|
|
|
mCentralContainer = new QStackedWidget;
|
|
mCentralContainer->insertWidget( 0, mMapCanvas );
|
|
mCentralContainer->insertWidget( 1, mWelcomePage );
|
|
|
|
centralLayout->addWidget( mCentralContainer, 0, 0, 2, 1 );
|
|
|
|
connect( mMapCanvas, &QgsMapCanvas::layersChanged, this, &QgisApp::showMapCanvas );
|
|
|
|
mCentralContainer->setCurrentIndex( mProjOpen ? 0 : 1 );
|
|
|
|
// a bar to warn the user with non-blocking messages
|
|
startProfile( QStringLiteral( "Message bar" ) );
|
|
mInfoBar = new QgsMessageBar( centralWidget );
|
|
mInfoBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
|
|
centralLayout->addWidget( mInfoBar, 0, 0, 1, 1 );
|
|
endProfile();
|
|
|
|
startProfile( QStringLiteral( "User input dock" ) );
|
|
// User Input Dock Widget
|
|
mUserInputDockWidget = new QgsUserInputDockWidget( this );
|
|
mUserInputDockWidget->setObjectName( QStringLiteral( "UserInputDockWidget" ) );
|
|
endProfile();
|
|
|
|
//set the focus to the map canvas
|
|
mMapCanvas->setFocus();
|
|
|
|
startProfile( QStringLiteral( "Layer tree" ) );
|
|
mLayerTreeView = new QgsLayerTreeView( this );
|
|
mLayerTreeView->setObjectName( QStringLiteral( "theLayerTreeView" ) ); // "theLayerTreeView" used to find this canonical instance later
|
|
endProfile();
|
|
|
|
// create undo widget
|
|
startProfile( QStringLiteral( "Undo dock" ) );
|
|
mUndoDock = new QgsDockWidget( tr( "Undo/Redo Panel" ), this );
|
|
mUndoWidget = new QgsUndoWidget( mUndoDock, mMapCanvas );
|
|
mUndoWidget->setObjectName( QStringLiteral( "Undo" ) );
|
|
mUndoDock->setWidget( mUndoWidget );
|
|
mUndoDock->setObjectName( QStringLiteral( "undo/redo dock" ) );
|
|
endProfile();
|
|
|
|
// Advanced Digitizing dock
|
|
startProfile( QStringLiteral( "Advanced digitize panel" ) );
|
|
mAdvancedDigitizingDockWidget = new QgsAdvancedDigitizingDockWidget( mMapCanvas, this );
|
|
mAdvancedDigitizingDockWidget->setObjectName( QStringLiteral( "AdvancedDigitizingTools" ) );
|
|
endProfile();
|
|
|
|
// Statistical Summary dock
|
|
startProfile( QStringLiteral( "Stats dock" ) );
|
|
mStatisticalSummaryDockWidget = new QgsStatisticalSummaryDockWidget( this );
|
|
mStatisticalSummaryDockWidget->setObjectName( QStringLiteral( "StatistalSummaryDockWidget" ) );
|
|
endProfile();
|
|
|
|
// Bookmarks dock
|
|
startProfile( QStringLiteral( "Bookmarks widget" ) );
|
|
mBookMarksDockWidget = new QgsBookmarks( this );
|
|
mBookMarksDockWidget->setObjectName( QStringLiteral( "BookmarksDockWidget" ) );
|
|
endProfile();
|
|
|
|
startProfile( QStringLiteral( "Snapping utils" ) );
|
|
mSnappingUtils = new QgsMapCanvasSnappingUtils( mMapCanvas, this );
|
|
mMapCanvas->setSnappingUtils( mSnappingUtils );
|
|
connect( QgsProject::instance(), &QgsProject::snappingConfigChanged, mSnappingUtils, &QgsSnappingUtils::setConfig );
|
|
connect( mSnappingUtils, &QgsSnappingUtils::configChanged, QgsProject::instance(), &QgsProject::setSnappingConfig );
|
|
|
|
|
|
endProfile();
|
|
|
|
functionProfile( &QgisApp::createMenus, this, QStringLiteral( "Create menus" ) );
|
|
functionProfile( &QgisApp::createActions, this, QStringLiteral( "Create actions" ) );
|
|
functionProfile( &QgisApp::createActionGroups, this, QStringLiteral( "Create action group" ) );
|
|
functionProfile( &QgisApp::createToolBars, this, QStringLiteral( "Toolbars" ) );
|
|
functionProfile( &QgisApp::createStatusBar, this, QStringLiteral( "Status bar" ) );
|
|
functionProfile( &QgisApp::createCanvasTools, this, QStringLiteral( "Create canvas tools" ) );
|
|
|
|
mMapCanvas->freeze();
|
|
applyDefaultSettingsToCanvas( mMapCanvas );
|
|
|
|
functionProfile( &QgisApp::initLayerTreeView, this, QStringLiteral( "Init Layer tree view" ) );
|
|
functionProfile( &QgisApp::createOverview, this, QStringLiteral( "Create overview" ) );
|
|
functionProfile( &QgisApp::createMapTips, this, QStringLiteral( "Create map tips" ) );
|
|
functionProfile( &QgisApp::createDecorations, this, QStringLiteral( "Create decorations" ) );
|
|
functionProfile( &QgisApp::readSettings, this, QStringLiteral( "Read settings" ) );
|
|
functionProfile( &QgisApp::updateRecentProjectPaths, this, QStringLiteral( "Update recent project paths" ) );
|
|
functionProfile( &QgisApp::updateProjectFromTemplates, this, QStringLiteral( "Update project from templates" ) );
|
|
functionProfile( &QgisApp::legendLayerSelectionChanged, this, QStringLiteral( "Legend layer selection changed" ) );
|
|
functionProfile( &QgisApp::init3D, this, QStringLiteral( "Initialize 3D support" ) );
|
|
|
|
QgsApplication::annotationRegistry()->addAnnotationType( QgsAnnotationMetadata( QStringLiteral( "FormAnnotationItem" ), &QgsFormAnnotation::create ) );
|
|
connect( QgsProject::instance()->annotationManager(), &QgsAnnotationManager::annotationAdded, this, &QgisApp::annotationCreated );
|
|
|
|
mSaveRollbackInProgress = false;
|
|
|
|
QString templateDirName = settings.value( QStringLiteral( "qgis/projectTemplateDir" ),
|
|
QgsApplication::qgisSettingsDirPath() + "project_templates" ).toString();
|
|
if ( !QFileInfo::exists( templateDirName ) )
|
|
{
|
|
// create default template directory
|
|
if ( !QDir().mkdir( QgsApplication::qgisSettingsDirPath() + "project_templates" ) )
|
|
templateDirName.clear();
|
|
}
|
|
if ( !templateDirName.isEmpty() ) // template directory exists, so watch it!
|
|
{
|
|
QFileSystemWatcher *projectsTemplateWatcher = new QFileSystemWatcher( this );
|
|
projectsTemplateWatcher->addPath( templateDirName );
|
|
connect( projectsTemplateWatcher, &QFileSystemWatcher::directoryChanged, this, [this] { updateProjectFromTemplates(); } );
|
|
}
|
|
|
|
// initialize the plugin manager
|
|
startProfile( QStringLiteral( "Plugin manager" ) );
|
|
mPluginManager = new QgsPluginManager( this, restorePlugins );
|
|
endProfile();
|
|
|
|
addDockWidget( Qt::LeftDockWidgetArea, mUndoDock );
|
|
mUndoDock->hide();
|
|
|
|
startProfile( QStringLiteral( "Layer Style dock" ) );
|
|
mMapStylingDock = new QgsDockWidget( this );
|
|
mMapStylingDock->setWindowTitle( tr( "Layer Styling" ) );
|
|
mMapStylingDock->setObjectName( QStringLiteral( "LayerStyling" ) );
|
|
mMapStyleWidget = new QgsLayerStylingWidget( mMapCanvas, mMapLayerPanelFactories );
|
|
mMapStylingDock->setWidget( mMapStyleWidget );
|
|
connect( mMapStyleWidget, &QgsLayerStylingWidget::styleChanged, this, &QgisApp::updateLabelToolButtons );
|
|
connect( mMapStylingDock, &QDockWidget::visibilityChanged, mActionStyleDock, &QAction::setChecked );
|
|
|
|
addDockWidget( Qt::RightDockWidgetArea, mMapStylingDock );
|
|
mMapStylingDock->hide();
|
|
endProfile();
|
|
|
|
startProfile( QStringLiteral( "Snapping dialog" ) );
|
|
mSnappingDialog = new QgsSnappingWidget( QgsProject::instance(), mMapCanvas, this );
|
|
connect( mSnappingDialog, &QgsSnappingWidget::snappingConfigChanged, QgsProject::instance(), [ = ] { QgsProject::instance()->setSnappingConfig( mSnappingDialog->config() ); } );
|
|
QString mainSnappingWidgetMode = QgsSettings().value( QStringLiteral( "/qgis/mainSnappingWidgetMode" ), "dialog" ).toString();
|
|
if ( mainSnappingWidgetMode == QLatin1String( "dock" ) )
|
|
{
|
|
QgsDockWidget *dock = new QgsDockWidget( tr( "Snapping and Digitizing Options" ), QgisApp::instance() );
|
|
dock->setAllowedAreas( Qt::AllDockWidgetAreas );
|
|
dock->setWidget( mSnappingDialog );
|
|
dock->setObjectName( QStringLiteral( "Snapping and Digitizing Options" ) );
|
|
addDockWidget( Qt::LeftDockWidgetArea, dock );
|
|
mSnappingDialogContainer = dock;
|
|
dock->hide();
|
|
}
|
|
else
|
|
{
|
|
QDialog *dialog = new QDialog( this );
|
|
dialog->setWindowTitle( tr( "Project Snapping Settings" ) );
|
|
QVBoxLayout *layout = new QVBoxLayout( dialog );
|
|
layout->addWidget( mSnappingDialog );
|
|
layout->setMargin( 0 );
|
|
mSnappingDialogContainer = dialog;
|
|
}
|
|
endProfile();
|
|
|
|
mBrowserModel = new QgsBrowserModel( this );
|
|
mBrowserWidget = new QgsBrowserDockWidget( tr( "Browser Panel" ), mBrowserModel, this );
|
|
mBrowserWidget->setObjectName( QStringLiteral( "Browser" ) );
|
|
addDockWidget( Qt::LeftDockWidgetArea, mBrowserWidget );
|
|
mBrowserWidget->hide();
|
|
connect( this, &QgisApp::newProject, mBrowserWidget, &QgsBrowserDockWidget::updateProjectHome );
|
|
// Only connect the first widget: the model is shared, there is no need to refresh multiple times.
|
|
connect( this, &QgisApp::connectionsChanged, mBrowserWidget, &QgsBrowserDockWidget::refresh );
|
|
connect( mBrowserWidget, &QgsBrowserDockWidget::connectionsChanged, this, &QgisApp::connectionsChanged );
|
|
connect( mBrowserWidget, &QgsBrowserDockWidget::openFile, this, &QgisApp::openFile );
|
|
connect( mBrowserWidget, &QgsBrowserDockWidget::handleDropUriList, this, &QgisApp::handleDropUriList );
|
|
|
|
mBrowserWidget2 = new QgsBrowserDockWidget( tr( "Browser Panel (2)" ), mBrowserModel, this );
|
|
mBrowserWidget2->setObjectName( QStringLiteral( "Browser2" ) );
|
|
addDockWidget( Qt::LeftDockWidgetArea, mBrowserWidget2 );
|
|
mBrowserWidget2->hide();
|
|
connect( this, &QgisApp::newProject, mBrowserWidget2, &QgsBrowserDockWidget::updateProjectHome );
|
|
connect( mBrowserWidget2, &QgsBrowserDockWidget::connectionsChanged, this, &QgisApp::connectionsChanged );
|
|
connect( mBrowserWidget2, &QgsBrowserDockWidget::openFile, this, &QgisApp::openFile );
|
|
connect( mBrowserWidget2, &QgsBrowserDockWidget::handleDropUriList, this, &QgisApp::handleDropUriList );
|
|
|
|
addDockWidget( Qt::LeftDockWidgetArea, mAdvancedDigitizingDockWidget );
|
|
mAdvancedDigitizingDockWidget->hide();
|
|
|
|
addDockWidget( Qt::LeftDockWidgetArea, mStatisticalSummaryDockWidget );
|
|
mStatisticalSummaryDockWidget->hide();
|
|
|
|
addDockWidget( Qt::LeftDockWidgetArea, mBookMarksDockWidget );
|
|
mBookMarksDockWidget->hide();
|
|
|
|
QMainWindow::addDockWidget( Qt::BottomDockWidgetArea, mUserInputDockWidget );
|
|
mUserInputDockWidget->setFloating( true );
|
|
|
|
// create the GPS tool on starting QGIS - this is like the browser
|
|
mpGpsWidget = new QgsGPSInformationWidget( mMapCanvas );
|
|
//create the dock widget
|
|
mpGpsDock = new QgsDockWidget( tr( "GPS Information Panel" ), this );
|
|
mpGpsDock->setObjectName( QStringLiteral( "GPSInformation" ) );
|
|
mpGpsDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
|
|
addDockWidget( Qt::LeftDockWidgetArea, mpGpsDock );
|
|
// add to the Panel submenu
|
|
// now add our widget to the dock - ownership of the widget is passed to the dock
|
|
mpGpsDock->setWidget( mpGpsWidget );
|
|
mpGpsDock->hide();
|
|
|
|
mLastMapToolMessage = nullptr;
|
|
|
|
mLogViewer = new QgsMessageLogViewer( this );
|
|
|
|
mLogDock = new QgsDockWidget( tr( "Log Messages Panel" ), this );
|
|
mLogDock->setObjectName( QStringLiteral( "MessageLog" ) );
|
|
mLogDock->setAllowedAreas( Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea );
|
|
addDockWidget( Qt::BottomDockWidgetArea, mLogDock );
|
|
mLogDock->setWidget( mLogViewer );
|
|
mLogDock->hide();
|
|
connect( mMessageButton, &QAbstractButton::toggled, mLogDock, &QWidget::setVisible );
|
|
connect( mLogDock, &QDockWidget::visibilityChanged, mMessageButton, &QAbstractButton::setChecked );
|
|
connect( QgsApplication::messageLog(), static_cast < void ( QgsMessageLog::* )( bool ) >( &QgsMessageLog::messageReceived ), this, &QgisApp::toggleLogMessageIcon );
|
|
connect( mMessageButton, &QAbstractButton::toggled, this, &QgisApp::toggleLogMessageIcon );
|
|
mVectorLayerTools = new QgsGuiVectorLayerTools();
|
|
|
|
// Init the editor widget types
|
|
QgsGui::editorWidgetRegistry()->initEditors( mMapCanvas, mInfoBar );
|
|
|
|
mInternalClipboard = new QgsClipboard; // create clipboard
|
|
connect( mInternalClipboard, &QgsClipboard::changed, this, &QgisApp::clipboardChanged );
|
|
mQgisInterface = new QgisAppInterface( this ); // create the interface
|
|
|
|
#ifdef Q_OS_MAC
|
|
// action for Window menu (create before generating WindowTitleChange event))
|
|
mWindowAction = new QAction( this );
|
|
connect( mWindowAction, SIGNAL( triggered() ), this, SLOT( activate() ) );
|
|
|
|
// add this window to Window menu
|
|
addWindow( mWindowAction );
|
|
#endif
|
|
|
|
activateDeactivateLayerRelatedActions( nullptr ); // after members were created
|
|
|
|
connect( QgsGui::mapLayerActionRegistry(), &QgsMapLayerActionRegistry::changed, this, &QgisApp::refreshActionFeatureAction );
|
|
|
|
// set application's caption
|
|
QString caption = tr( "QGIS - %1 ('%2')" ).arg( Qgis::QGIS_VERSION, Qgis::QGIS_RELEASE_NAME );
|
|
setWindowTitle( caption );
|
|
|
|
QgsMessageLog::logMessage( tr( "QGIS starting..." ), QString(), QgsMessageLog::INFO );
|
|
|
|
// set QGIS specific srs validation
|
|
connect( this, &QgisApp::customCrsValidation,
|
|
this, &QgisApp::validateCrs );
|
|
QgsCoordinateReferenceSystem::setCustomCrsValidation( customSrsValidation_ );
|
|
|
|
// set graphical message output
|
|
QgsMessageOutput::setMessageOutputCreator( messageOutputViewer_ );
|
|
|
|
// set graphical credential requester
|
|
new QgsCredentialDialog( this );
|
|
|
|
mLocatorWidget->setMapCanvas( mMapCanvas );
|
|
connect( mLocatorWidget, &QgsLocatorWidget::configTriggered, this, [ = ] { showOptionsDialog( this, QStringLiteral( "mOptionsLocatorSettings" ) ); } );
|
|
|
|
qApp->processEvents();
|
|
|
|
// load providers
|
|
mSplash->showMessage( tr( "Checking provider plugins" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
qApp->processEvents();
|
|
QgsApplication::initQgis();
|
|
|
|
QgsApplication::dataItemProviderRegistry()->addProvider( new QgsQlrDataItemProvider() );
|
|
registerCustomDropHandler( new QgsQlrDropHandler() );
|
|
QgsApplication::dataItemProviderRegistry()->addProvider( new QgsQptDataItemProvider() );
|
|
registerCustomDropHandler( new QgsQptDropHandler() );
|
|
mSplash->showMessage( tr( "Starting Python" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
qApp->processEvents();
|
|
loadPythonSupport();
|
|
|
|
#ifdef WITH_BINDINGS
|
|
QgsApplication::dataItemProviderRegistry()->addProvider( new QgsPyDataItemProvider() );
|
|
registerCustomDropHandler( new QgsPyDropHandler() );
|
|
#endif
|
|
|
|
// Create the plugin registry and load plugins
|
|
// load any plugins that were running in the last session
|
|
mSplash->showMessage( tr( "Restoring loaded plugins" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
qApp->processEvents();
|
|
QgsPluginRegistry::instance()->setQgisInterface( mQgisInterface );
|
|
if ( restorePlugins )
|
|
{
|
|
// Restoring of plugins can be disabled with --noplugins command line option
|
|
// because some plugins may cause QGIS to crash during startup
|
|
QgsPluginRegistry::instance()->restoreSessionPlugins( QgsApplication::pluginPath() );
|
|
|
|
// Also restore plugins from user specified plugin directories
|
|
QString myPaths = settings.value( QStringLiteral( "plugins/searchPathsForPlugins" ), "" ).toString();
|
|
if ( !myPaths.isEmpty() )
|
|
{
|
|
QStringList myPathList = myPaths.split( '|' );
|
|
QgsPluginRegistry::instance()->restoreSessionPlugins( myPathList );
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_BINDINGS
|
|
if ( mPythonUtils && mPythonUtils->isEnabled() )
|
|
{
|
|
startProfile( QStringLiteral( "initPluginInstaller" ) );
|
|
// initialize the plugin installer to start fetching repositories in background
|
|
QgsPythonRunner::run( QStringLiteral( "import pyplugin_installer" ) );
|
|
QgsPythonRunner::run( QStringLiteral( "pyplugin_installer.initPluginInstaller()" ) );
|
|
// enable Python in the Plugin Manager and pass the PythonUtils to it
|
|
mPluginManager->setPythonUtils( mPythonUtils );
|
|
endProfile();
|
|
}
|
|
else if ( mActionShowPythonDialog || mActionInstallFromZip )
|
|
#endif
|
|
{
|
|
// python is disabled so get rid of the action for python console
|
|
// and installing plugin from ZUIP
|
|
delete mActionShowPythonDialog;
|
|
delete mActionInstallFromZip;
|
|
mActionShowPythonDialog = nullptr;
|
|
mActionInstallFromZip = nullptr;
|
|
}
|
|
|
|
// Set icon size of toolbars
|
|
if ( settings.contains( QStringLiteral( "IconSize" ) ) )
|
|
{
|
|
int size = settings.value( QStringLiteral( "IconSize" ) ).toInt();
|
|
if ( size < 16 )
|
|
size = QGIS_ICON_SIZE;
|
|
setIconSizes( size );
|
|
}
|
|
else
|
|
{
|
|
// first run, guess a good icon size
|
|
int size = chooseReasonableDefaultIconSize();
|
|
settings.setValue( QStringLiteral( "IconSize" ), size );
|
|
setIconSizes( size );
|
|
}
|
|
|
|
mSplash->showMessage( tr( "Initializing file filters" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
qApp->processEvents();
|
|
|
|
// now build vector and raster file filters
|
|
mVectorFileFilter = QgsProviderRegistry::instance()->fileVectorFilters();
|
|
mRasterFileFilter = QgsProviderRegistry::instance()->fileRasterFilters();
|
|
|
|
// set handler for missing layers (will be owned by QgsProject)
|
|
QgsProject::instance()->setBadLayerHandler( new QgsHandleBadLayersHandler() );
|
|
|
|
#if 0
|
|
// Set the background color for toolbox and overview as they default to
|
|
// white instead of the window color
|
|
QPalette myPalette = toolBox->palette();
|
|
myPalette.setColor( QPalette::Button, myPalette.window().color() );
|
|
toolBox->setPalette( myPalette );
|
|
//do the same for legend control
|
|
myPalette = toolBox->palette();
|
|
myPalette.setColor( QPalette::Button, myPalette.window().color() );
|
|
mMapLegend->setPalette( myPalette );
|
|
//and for overview control this is done in createOverView method
|
|
#endif
|
|
// Do this last in the ctor to ensure that all members are instantiated properly
|
|
setupConnections();
|
|
//
|
|
// Please make sure this is the last thing the ctor does so that we can ensure the
|
|
// widgets are all initialized before trying to restore their state.
|
|
//
|
|
mSplash->showMessage( tr( "Restoring window state" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
qApp->processEvents();
|
|
startProfile( QStringLiteral( "Restore window state" ) );
|
|
restoreWindowState();
|
|
endProfile();
|
|
|
|
// do main window customization - after window state has been restored, before the window is shown
|
|
startProfile( QStringLiteral( "Update customiziation on main window" ) );
|
|
QgsCustomization::instance()->updateMainWindow( mToolbarMenu );
|
|
endProfile();
|
|
|
|
mSplash->showMessage( tr( "Populate saved styles" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
startProfile( QStringLiteral( "Populate saved styles" ) );
|
|
QgsStyle::defaultStyle();
|
|
endProfile();
|
|
|
|
mSplash->showMessage( tr( "QGIS Ready!" ), Qt::AlignHCenter | Qt::AlignBottom );
|
|
|
|
QgsMessageLog::logMessage( QgsApplication::showSettings(), QString(), QgsMessageLog::INFO );
|
|
|
|
QgsMessageLog::logMessage( tr( "QGIS Ready!" ), QString(), QgsMessageLog::INFO );
|
|
|
|
mMapTipsVisible = false;
|
|
// This turns on the map tip if they where active in the last session
|
|
if ( settings.value( QStringLiteral( "qgis/enableMapTips" ), false ).toBool() )
|
|
{
|
|
toggleMapTips( true );
|
|
}
|
|
|
|
mTrustedMacros = false;
|
|
|
|
// setup drag drop
|
|
setAcceptDrops( true );
|
|
|
|
mFullScreenMode = false;
|
|
mPrevScreenModeMaximized = false;
|
|
startProfile( QStringLiteral( "Show main window" ) );
|
|
show();
|
|
qApp->processEvents();
|
|
endProfile();
|
|
|
|
mMapCanvas->freeze( false );
|
|
mMapCanvas->clearExtentHistory(); // reset zoomnext/zoomlast
|
|
|
|
QShortcut *zoomInShortCut = new QShortcut( QKeySequence( tr( "Ctrl++" ) ), this );
|
|
connect( zoomInShortCut, &QShortcut::activated, mMapCanvas, &QgsMapCanvas::zoomIn );
|
|
zoomInShortCut->setObjectName( QStringLiteral( "ZoomInToCanvas" ) );
|
|
zoomInShortCut->setWhatsThis( tr( "Zoom in to canvas" ) );
|
|
zoomInShortCut->setProperty( "Icon", QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomIn.svg" ) ) );
|
|
|
|
QShortcut *zoomShortCut2 = new QShortcut( QKeySequence( tr( "Ctrl+=" ) ), this );
|
|
connect( zoomShortCut2, &QShortcut::activated, mMapCanvas, &QgsMapCanvas::zoomIn );
|
|
zoomShortCut2->setObjectName( QStringLiteral( "ZoomInToCanvas2" ) );
|
|
zoomShortCut2->setWhatsThis( tr( "Zoom in to canvas (secondary)" ) );
|
|
zoomShortCut2->setProperty( "Icon", QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomIn.svg" ) ) );
|
|
|
|
QShortcut *zoomOutShortCut = new QShortcut( QKeySequence( tr( "Ctrl+-" ) ), this );
|
|
connect( zoomOutShortCut, &QShortcut::activated, mMapCanvas, &QgsMapCanvas::zoomOut );
|
|
zoomOutShortCut->setObjectName( QStringLiteral( "ZoomOutOfCanvas" ) );
|
|
zoomOutShortCut->setWhatsThis( tr( "Zoom out of canvas" ) );
|
|
zoomOutShortCut->setProperty( "Icon", QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomOut.svg" ) ) );
|
|
|
|
//also make ctrl+alt+= a shortcut to switch to zoom in map tool
|
|
QShortcut *zoomInToolShortCut = new QShortcut( QKeySequence( tr( "Ctrl+Alt+=" ) ), this );
|
|
connect( zoomInToolShortCut, &QShortcut::activated, this, &QgisApp::zoomIn );
|
|
zoomInToolShortCut->setObjectName( QStringLiteral( "ZoomIn2" ) );
|
|
zoomInToolShortCut->setWhatsThis( tr( "Zoom in (secondary)" ) );
|
|
zoomInToolShortCut->setProperty( "Icon", QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomIn.svg" ) ) );
|
|
|
|
QShortcut *toggleSnapping = new QShortcut( QKeySequence( tr( "S" ) ), this );
|
|
toggleSnapping->setObjectName( QStringLiteral( "toggleSnapping" ) );
|
|
toggleSnapping->setWhatsThis( tr( "Toggle snapping" ) );
|
|
toggleSnapping->setProperty( "Icon", QgsApplication::getThemeIcon( QStringLiteral( "/mIconSnapping.svg" ) ) );
|
|
connect( toggleSnapping, &QShortcut::activated, mSnappingUtils, &QgsSnappingUtils::toggleEnabled );
|
|
|
|
if ( ! QTouchDevice::devices().isEmpty() )
|
|
{
|
|
//add reacting to long click in touch
|
|
grabGesture( Qt::TapAndHoldGesture );
|
|
}
|
|
|
|
connect( QgsApplication::taskManager(), &QgsTaskManager::statusChanged, this, &QgisApp::onTaskCompleteShowNotify );
|
|
|
|
#ifdef Q_OS_WIN
|
|
QWinTaskbarButton *taskButton = new QWinTaskbarButton( this );
|
|
taskButton->setWindow( windowHandle() );
|
|
|
|
QWinTaskbarProgress *taskProgress = taskButton->progress();
|
|
taskProgress->setVisible( false );
|
|
connect( QgsApplication::taskManager(), &QgsTaskManager::taskAdded, taskProgress, [taskProgress] { taskProgress->setMaximum( 0 ); taskProgress->show(); }
|
|
);
|
|
connect( QgsApplication::taskManager(), &QgsTaskManager::finalTaskProgressChanged, taskProgress, [taskProgress]( double val ) { taskProgress->setMaximum( 100 ); taskProgress->show(); taskProgress->setValue( val ); }
|
|
);
|
|
connect( QgsApplication::taskManager(), &QgsTaskManager::allTasksFinished, taskProgress, &QWinTaskbarProgress::hide );
|
|
#endif
|
|
|
|
// supposedly all actions have been added, now register them to the shortcut manager
|
|
QgsGui::shortcutsManager()->registerAllChildren( this );
|
|
|
|
QgsProviderRegistry::instance()->registerGuis( this );
|
|
|
|
setupLayoutManagerConnections();
|
|
|
|
// update windows
|
|
qApp->processEvents();
|
|
|
|
// notify user if authentication system is disabled
|
|
( void )QgsAuthGuiUtils::isDisabled( messageBar() );
|
|
|
|
startProfile( QStringLiteral( "New project" ) );
|
|
fileNewBlank(); // prepare empty project, also skips any default templates from loading
|
|
endProfile();
|
|
|
|
// request notification of FileOpen events (double clicking a file icon in Mac OS X Finder)
|
|
// should come after fileNewBlank to ensure project is properly set up to receive any data source files
|
|
QgsApplication::setFileOpenEventReceiver( this );
|
|
|
|
|
|
#ifdef ANDROID
|
|
toggleFullScreen();
|
|
#endif
|
|
profiler->endGroup();
|
|
|
|
QgsDebugMsg( "PROFILE TIMES" );
|
|
QgsDebugMsg( QString( "PROFILE TIMES TOTAL - %1 " ).arg( profiler->totalTime() ) );
|
|
#ifdef QGISDEBUG
|
|
QList<QPair<QString, double> > profileTimes = profiler->profileTimes();
|
|
QList<QPair<QString, double> >::const_iterator it = profileTimes.constBegin();
|
|
for ( ; it != profileTimes.constEnd(); ++it )
|
|
{
|
|
QString name = ( *it ).first;
|
|
double time = ( *it ).second;
|
|
QgsDebugMsg( QString( " - %1 - %2" ).arg( name ).arg( time ) );
|
|
}
|
|
#endif
|
|
|
|
connect( qApp, &QApplication::focusChanged, this, &QgisApp::onFocusChanged );
|
|
} // QgisApp ctor
|
|
|
|
QgisApp::QgisApp()
|
|
: QMainWindow( nullptr, 0 )
|
|
#ifdef Q_OS_MAC
|
|
, mWindowMenu( nullptr )
|
|
#endif
|
|
{
|
|
sInstance = this;
|
|
setupUi( this );
|
|
mInternalClipboard = new QgsClipboard;
|
|
mMapCanvas = new QgsMapCanvas();
|
|
connect( mMapCanvas, &QgsMapCanvas::messageEmitted, this, &QgisApp::displayMessage );
|
|
mMapCanvas->freeze();
|
|
mLayerTreeView = new QgsLayerTreeView( this );
|
|
mUndoWidget = new QgsUndoWidget( nullptr, mMapCanvas );
|
|
mInfoBar = new QgsMessageBar( centralWidget() );
|
|
// More tests may need more members to be initialized
|
|
}
|
|
|
|
QgisApp::~QgisApp()
|
|
{
|
|
stopRendering();
|
|
|
|
delete mInternalClipboard;
|
|
delete mQgisInterface;
|
|
delete mStyleSheetBuilder;
|
|
|
|
delete mMapTools.mZoomIn;
|
|
delete mMapTools.mZoomOut;
|
|
delete mMapTools.mPan;
|
|
delete mMapTools.mAddFeature;
|
|
delete mMapTools.mAddPart;
|
|
delete mMapTools.mAddRing;
|
|
delete mMapTools.mFillRing;
|
|
delete mMapTools.mAnnotation;
|
|
delete mMapTools.mChangeLabelProperties;
|
|
delete mMapTools.mDeletePart;
|
|
delete mMapTools.mDeleteRing;
|
|
delete mMapTools.mFeatureAction;
|
|
delete mMapTools.mFormAnnotation;
|
|
delete mMapTools.mHtmlAnnotation;
|
|
delete mMapTools.mIdentify;
|
|
delete mMapTools.mMeasureAngle;
|
|
delete mMapTools.mMeasureArea;
|
|
delete mMapTools.mMeasureDist;
|
|
delete mMapTools.mMoveFeature;
|
|
delete mMapTools.mMoveFeatureCopy;
|
|
delete mMapTools.mMoveLabel;
|
|
delete mMapTools.mNodeTool;
|
|
delete mMapTools.mOffsetCurve;
|
|
delete mMapTools.mPinLabels;
|
|
delete mMapTools.mReshapeFeatures;
|
|
delete mMapTools.mRotateFeature;
|
|
delete mMapTools.mRotateLabel;
|
|
delete mMapTools.mRotatePointSymbolsTool;
|
|
delete mMapTools.mOffsetPointSymbolTool;
|
|
delete mMapTools.mSelectFreehand;
|
|
delete mMapTools.mSelectPolygon;
|
|
delete mMapTools.mSelectRadius;
|
|
delete mMapTools.mSelectFeatures;
|
|
delete mMapTools.mShowHideLabels;
|
|
delete mMapTools.mSimplifyFeature;
|
|
delete mMapTools.mSplitFeatures;
|
|
delete mMapTools.mSplitParts;
|
|
delete mMapTools.mSvgAnnotation;
|
|
delete mMapTools.mTextAnnotation;
|
|
delete mMapTools.mCircularStringCurvePoint;
|
|
delete mMapTools.mCircularStringRadius;
|
|
delete mMapTools.mCircle2Points;
|
|
delete mMapTools.mCircle3Points;
|
|
delete mMapTools.mCircle3Tangents;
|
|
delete mMapTools.mCircle2TangentsPoint;
|
|
delete mMapTools.mCircleCenterPoint;
|
|
delete mMapTools.mEllipseCenter2Points;
|
|
delete mMapTools.mEllipseCenterPoint;
|
|
delete mMapTools.mEllipseExtent;
|
|
delete mMapTools.mEllipseFoci;
|
|
delete mMapTools.mRectangleCenterPoint;
|
|
delete mMapTools.mRectangleExtent;
|
|
delete mMapTools.mRectangle3Points;
|
|
delete mMapTools.mRegularPolygon2Points;
|
|
delete mMapTools.mRegularPolygonCenterPoint;
|
|
delete mMapTools.mRegularPolygonCenterCorner;
|
|
delete mpMaptip;
|
|
|
|
delete mpGpsWidget;
|
|
|
|
delete mOverviewMapCursor;
|
|
|
|
delete mComposerManager;
|
|
|
|
delete mTracer;
|
|
|
|
delete mVectorLayerTools;
|
|
delete mWelcomePage;
|
|
|
|
deletePrintComposers();
|
|
removeAnnotationItems();
|
|
|
|
// cancel request for FileOpen events
|
|
QgsApplication::setFileOpenEventReceiver( nullptr );
|
|
|
|
QgsApplication::exitQgis();
|
|
|
|
delete QgsProject::instance();
|
|
|
|
delete mPythonUtils;
|
|
|
|
delete mTray;
|
|
|
|
delete mDataSourceManagerDialog;
|
|
}
|
|
|
|
void QgisApp::dragEnterEvent( QDragEnterEvent *event )
|
|
{
|
|
if ( event->mimeData()->hasUrls() || event->mimeData()->hasFormat( QStringLiteral( "application/x-vnd.qgis.qgis.uri" ) ) )
|
|
{
|
|
// the mime data are coming from layer tree, so ignore that, do not import those layers again
|
|
if ( !event->mimeData()->hasFormat( QStringLiteral( "application/qgis.layertreemodeldata" ) ) )
|
|
event->acceptProposedAction();
|
|
}
|
|
}
|
|
|
|
void QgisApp::dropEvent( QDropEvent *event )
|
|
{
|
|
// dragging app is locked for the duration of dropEvent. This causes explorer windows to hang
|
|
// while large projects/layers are loaded. So instead we return from dropEvent as quickly as possible
|
|
// and do the actual handling of the drop after a very short timeout
|
|
QTimer *timer = new QTimer( this );
|
|
timer->setSingleShot( true );
|
|
timer->setInterval( 50 );
|
|
|
|
// first, allow custom handlers to directly operate on the mime data
|
|
const QVector<QPointer<QgsCustomDropHandler >> handlers = mCustomDropHandlers;
|
|
for ( QgsCustomDropHandler *handler : handlers )
|
|
{
|
|
if ( handler )
|
|
handler->handleMimeData( event->mimeData() );
|
|
}
|
|
|
|
// get the file list
|
|
QList<QUrl>::iterator i;
|
|
QList<QUrl>urls = event->mimeData()->urls();
|
|
QStringList files;
|
|
for ( i = urls.begin(); i != urls.end(); ++i )
|
|
{
|
|
QString fileName = i->toLocalFile();
|
|
#ifdef Q_OS_MAC
|
|
// Mac OS X 10.10, under Qt4.8 ,changes dropped URL format
|
|
// https://bugreports.qt.io/browse/QTBUG-40449
|
|
// [pzion 20150805] Work around
|
|
if ( fileName.startsWith( "/.file/id=" ) )
|
|
{
|
|
QgsDebugMsg( "Mac dropped URL with /.file/id= (converting)" );
|
|
CFStringRef relCFStringRef =
|
|
CFStringCreateWithCString(
|
|
kCFAllocatorDefault,
|
|
fileName.toUtf8().constData(),
|
|
kCFStringEncodingUTF8
|
|
);
|
|
CFURLRef relCFURL =
|
|
CFURLCreateWithFileSystemPath(
|
|
kCFAllocatorDefault,
|
|
relCFStringRef,
|
|
kCFURLPOSIXPathStyle,
|
|
false // isDirectory
|
|
);
|
|
CFErrorRef error = 0;
|
|
CFURLRef absCFURL =
|
|
CFURLCreateFilePathURL(
|
|
kCFAllocatorDefault,
|
|
relCFURL,
|
|
&error
|
|
);
|
|
if ( !error )
|
|
{
|
|
static const CFIndex maxAbsPathCStrBufLen = 4096;
|
|
char absPathCStr[maxAbsPathCStrBufLen];
|
|
if ( CFURLGetFileSystemRepresentation(
|
|
absCFURL,
|
|
true, // resolveAgainstBase
|
|
reinterpret_cast<UInt8 *>( &absPathCStr[0] ),
|
|
maxAbsPathCStrBufLen ) )
|
|
{
|
|
fileName = QString( absPathCStr );
|
|
}
|
|
}
|
|
CFRelease( absCFURL );
|
|
CFRelease( relCFURL );
|
|
CFRelease( relCFStringRef );
|
|
}
|
|
#endif
|
|
// seems that some drag and drop operations include an empty url
|
|
// so we test for length to make sure we have something
|
|
if ( !fileName.isEmpty() )
|
|
{
|
|
files << fileName;
|
|
}
|
|
}
|
|
|
|
QgsMimeDataUtils::UriList lst;
|
|
if ( QgsMimeDataUtils::isUriList( event->mimeData() ) )
|
|
{
|
|
lst = QgsMimeDataUtils::decodeUriList( event->mimeData() );
|
|
}
|
|
|
|
connect( timer, &QTimer::timeout, this, [this, timer, files, lst]
|
|
{
|
|
freezeCanvases();
|
|
|
|
for ( const QString &file : qgsAsConst( files ) )
|
|
{
|
|
bool handled = false;
|
|
|
|
// give custom drop handlers first priority at handling the file
|
|
const QVector<QPointer<QgsCustomDropHandler >> handlers = mCustomDropHandlers;
|
|
for ( QgsCustomDropHandler *handler : handlers )
|
|
{
|
|
if ( handler && handler->handleFileDrop( file ) )
|
|
{
|
|
handled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !handled )
|
|
openFile( file );
|
|
}
|
|
|
|
if ( !lst.isEmpty() )
|
|
{
|
|
handleDropUriList( lst );
|
|
}
|
|
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
|
|
timer->deleteLater();
|
|
} );
|
|
|
|
event->acceptProposedAction();
|
|
timer->start();
|
|
}
|
|
|
|
void QgisApp::annotationCreated( QgsAnnotation *annotation )
|
|
{
|
|
// create canvas annotation item for annotation
|
|
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
|
|
{
|
|
QgsMapCanvasAnnotationItem *canvasItem = new QgsMapCanvasAnnotationItem( annotation, canvas );
|
|
Q_UNUSED( canvasItem ); //item is already added automatically to canvas scene
|
|
}
|
|
}
|
|
|
|
void QgisApp::registerCustomDropHandler( QgsCustomDropHandler *handler )
|
|
{
|
|
if ( !mCustomDropHandlers.contains( handler ) )
|
|
mCustomDropHandlers << handler;
|
|
}
|
|
|
|
void QgisApp::unregisterCustomDropHandler( QgsCustomDropHandler *handler )
|
|
{
|
|
mCustomDropHandlers.removeOne( handler );
|
|
}
|
|
|
|
void QgisApp::handleDropUriList( const QgsMimeDataUtils::UriList &lst )
|
|
{
|
|
// insert items in reverse order as each one is inserted on top of previous one
|
|
for ( int i = lst.size() - 1 ; i >= 0 ; i-- )
|
|
{
|
|
const QgsMimeDataUtils::Uri &u = lst.at( i );
|
|
|
|
QString uri = crsAndFormatAdjustedLayerUri( u.uri, u.supportedCrs, u.supportedFormats );
|
|
|
|
if ( u.layerType == QLatin1String( "vector" ) )
|
|
{
|
|
addVectorLayer( uri, u.name, u.providerKey );
|
|
}
|
|
else if ( u.layerType == QLatin1String( "raster" ) )
|
|
{
|
|
addRasterLayer( uri, u.name, u.providerKey );
|
|
}
|
|
else if ( u.layerType == QLatin1String( "plugin" ) )
|
|
{
|
|
addPluginLayer( uri, u.name, u.providerKey );
|
|
}
|
|
else if ( u.layerType == QLatin1String( "custom" ) )
|
|
{
|
|
Q_FOREACH ( QgsCustomDropHandler *handler, mCustomDropHandlers )
|
|
{
|
|
if ( handler && handler->customUriProviderKey() == u.providerKey )
|
|
{
|
|
handler->handleCustomUriDrop( u );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool QgisApp::event( QEvent *event )
|
|
{
|
|
bool done = false;
|
|
if ( event->type() == QEvent::FileOpen )
|
|
{
|
|
// handle FileOpen event (double clicking a file icon in Mac OS X Finder)
|
|
QFileOpenEvent *foe = static_cast<QFileOpenEvent *>( event );
|
|
openFile( foe->file() );
|
|
done = true;
|
|
}
|
|
else if ( !QTouchDevice::devices().isEmpty() && event->type() == QEvent::Gesture )
|
|
{
|
|
done = gestureEvent( static_cast<QGestureEvent *>( event ) );
|
|
}
|
|
else
|
|
{
|
|
// pass other events to base class
|
|
done = QMainWindow::event( event );
|
|
}
|
|
return done;
|
|
}
|
|
|
|
void QgisApp::dataSourceManager( const QString &pageName )
|
|
{
|
|
if ( ! mDataSourceManagerDialog )
|
|
{
|
|
mDataSourceManagerDialog = new QgsDataSourceManagerDialog( mBrowserModel, this, mapCanvas() );
|
|
connect( this, &QgisApp::connectionsChanged, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::refresh );
|
|
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::connectionsChanged, this, &QgisApp::connectionsChanged );
|
|
connect( mDataSourceManagerDialog, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ),
|
|
this, SLOT( addRasterLayer( QString const &, QString const &, QString const & ) ) );
|
|
connect( mDataSourceManagerDialog, SIGNAL( addVectorLayer( QString const &, QString const &, QString const & ) ),
|
|
this, SLOT( addVectorLayer( QString const &, QString const &, QString const & ) ) );
|
|
connect( mDataSourceManagerDialog, SIGNAL( addVectorLayers( QStringList const &, QString const &, QString const & ) ),
|
|
this, SLOT( addVectorLayers( QStringList const &, QString const &, QString const & ) ) );
|
|
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::showProgress, this, &QgisApp::showProgress );
|
|
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::showStatusMessage, this, &QgisApp::showStatusMessage );
|
|
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addDatabaseLayers, this, &QgisApp::addDatabaseLayers );
|
|
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::replaceSelectedVectorLayer, this, &QgisApp::replaceSelectedVectorLayer );
|
|
connect( mDataSourceManagerDialog, static_cast<void ( QgsDataSourceManagerDialog::* )()>( &QgsDataSourceManagerDialog::addRasterLayer ), this, static_cast<void ( QgisApp::* )()>( &QgisApp::addRasterLayer ) );
|
|
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::handleDropUriList, this, &QgisApp::handleDropUriList );
|
|
connect( this, &QgisApp::newProject, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::updateProjectHome );
|
|
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::openFile, this, &QgisApp::openFile );
|
|
|
|
}
|
|
// Try to open the dialog on a particular page
|
|
if ( ! pageName.isEmpty() )
|
|
{
|
|
mDataSourceManagerDialog->openPage( pageName );
|
|
}
|
|
if ( QgsSettings().value( QStringLiteral( "/qgis/dataSourceManagerNonModal" ), true ).toBool() )
|
|
{
|
|
mDataSourceManagerDialog->show();
|
|
}
|
|
else
|
|
{
|
|
mDataSourceManagerDialog->exec();
|
|
}
|
|
}
|
|
|
|
QgisAppStyleSheet *QgisApp::styleSheetBuilder()
|
|
{
|
|
Q_ASSERT( mStyleSheetBuilder );
|
|
return mStyleSheetBuilder;
|
|
}
|
|
|
|
void QgisApp::readRecentProjects()
|
|
{
|
|
QgsSettings settings;
|
|
mRecentProjects.clear();
|
|
|
|
settings.beginGroup( QStringLiteral( "UI" ) );
|
|
|
|
// Migrate old recent projects if first time with new system
|
|
if ( !settings.childGroups().contains( QStringLiteral( "recentProjects" ) ) )
|
|
{
|
|
QStringList oldRecentProjects = settings.value( QStringLiteral( "UI/recentProjectsList" ) ).toStringList();
|
|
|
|
Q_FOREACH ( const QString &project, oldRecentProjects )
|
|
{
|
|
QgsWelcomePageItemsModel::RecentProjectData data;
|
|
data.path = project;
|
|
data.title = project;
|
|
|
|
mRecentProjects.append( data );
|
|
}
|
|
}
|
|
settings.endGroup();
|
|
|
|
settings.beginGroup( QStringLiteral( "UI/recentProjects" ) );
|
|
QStringList projectKeysList = settings.childGroups();
|
|
|
|
//convert list to int values to obtain proper order
|
|
QList<int> projectKeys;
|
|
Q_FOREACH ( const QString &key, projectKeysList )
|
|
{
|
|
projectKeys.append( key.toInt() );
|
|
}
|
|
std::sort( projectKeys.begin(), projectKeys.end() );
|
|
|
|
Q_FOREACH ( int key, projectKeys )
|
|
{
|
|
QgsWelcomePageItemsModel::RecentProjectData data;
|
|
settings.beginGroup( QString::number( key ) );
|
|
data.title = settings.value( QStringLiteral( "title" ) ).toString();
|
|
data.path = settings.value( QStringLiteral( "path" ) ).toString();
|
|
data.previewImagePath = settings.value( QStringLiteral( "previewImage" ) ).toString();
|
|
data.crs = settings.value( QStringLiteral( "crs" ) ).toString();
|
|
settings.endGroup();
|
|
mRecentProjects.append( data );
|
|
}
|
|
settings.endGroup();
|
|
}
|
|
|
|
void QgisApp::applyProjectSettingsToCanvas( QgsMapCanvas *canvas )
|
|
{
|
|
int red = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), 255 );
|
|
int green = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), 255 );
|
|
int blue = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), 255 );
|
|
QColor myColor = QColor( red, green, blue );
|
|
canvas->setCanvasColor( myColor );
|
|
|
|
int alpha = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), 255 );
|
|
red = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), 255 );
|
|
green = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), 255 );
|
|
blue = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), 0 );
|
|
myColor = QColor( red, green, blue, alpha );
|
|
canvas->setSelectionColor( myColor );
|
|
}
|
|
|
|
void QgisApp::applyDefaultSettingsToCanvas( QgsMapCanvas *canvas )
|
|
{
|
|
QgsSettings settings;
|
|
canvas->enableAntiAliasing( settings.value( QStringLiteral( "qgis/enable_anti_aliasing" ), true ).toBool() );
|
|
double zoomFactor = settings.value( QStringLiteral( "qgis/zoom_factor" ), 2 ).toDouble();
|
|
canvas->setWheelFactor( zoomFactor );
|
|
canvas->setCachingEnabled( settings.value( QStringLiteral( "qgis/enable_render_caching" ), true ).toBool() );
|
|
canvas->setParallelRenderingEnabled( settings.value( QStringLiteral( "qgis/parallel_rendering" ), true ).toBool() );
|
|
canvas->setMapUpdateInterval( settings.value( QStringLiteral( "qgis/map_update_interval" ), 250 ).toInt() );
|
|
canvas->setSegmentationTolerance( settings.value( QStringLiteral( "qgis/segmentationTolerance" ), "0.01745" ).toDouble() );
|
|
canvas->setSegmentationToleranceType( QgsAbstractGeometry::SegmentationToleranceType( settings.value( QStringLiteral( "qgis/segmentationToleranceType" ), "0" ).toInt() ) );
|
|
}
|
|
|
|
int QgisApp::chooseReasonableDefaultIconSize() const
|
|
{
|
|
QScreen *screen = QApplication::screens().at( 0 );
|
|
if ( screen->physicalDotsPerInch() < 115 )
|
|
{
|
|
// no hidpi screen, use default size
|
|
return QGIS_ICON_SIZE;
|
|
}
|
|
else
|
|
{
|
|
double size = fontMetrics().width( QStringLiteral( "XXX" ) );
|
|
if ( size < 24 )
|
|
return 16;
|
|
else if ( size < 32 )
|
|
return 24;
|
|
else if ( size < 48 )
|
|
return 32;
|
|
else if ( size < 64 )
|
|
return 48;
|
|
else
|
|
return 64;
|
|
}
|
|
|
|
}
|
|
|
|
int QgisApp::dockedToolbarIconSize( int standardToolbarIconSize ) const
|
|
{
|
|
int dockSize;
|
|
if ( standardToolbarIconSize > 32 )
|
|
{
|
|
dockSize = standardToolbarIconSize - 16;
|
|
}
|
|
else if ( standardToolbarIconSize == 32 )
|
|
{
|
|
dockSize = 24;
|
|
}
|
|
else
|
|
{
|
|
dockSize = 16;
|
|
}
|
|
return dockSize;
|
|
}
|
|
|
|
void QgisApp::readSettings()
|
|
{
|
|
QgsSettings settings;
|
|
QString themename = settings.value( QStringLiteral( "UI/UITheme" ), "default" ).toString();
|
|
setTheme( themename );
|
|
|
|
// Read legacy settings
|
|
readRecentProjects();
|
|
|
|
// this is a new session! reset enable macros value to "ask"
|
|
// whether set to "just for this session"
|
|
if ( settings.value( QStringLiteral( "qgis/enableMacros" ), 1 ).toInt() == 2 )
|
|
{
|
|
settings.setValue( QStringLiteral( "qgis/enableMacros" ), 1 );
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Set Up the gui toolbars, menus, statusbar etc
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void QgisApp::createActions()
|
|
{
|
|
mActionPluginSeparator1 = nullptr; // plugin list separator will be created when the first plugin is loaded
|
|
mActionPluginSeparator2 = nullptr; // python separator will be created only if python is found
|
|
mActionRasterSeparator = nullptr; // raster plugins list separator will be created when the first plugin is loaded
|
|
|
|
// Project Menu Items
|
|
|
|
connect( mActionNewProject, &QAction::triggered, this, [ = ] { fileNew(); } );
|
|
connect( mActionNewBlankProject, &QAction::triggered, this, &QgisApp::fileNewBlank );
|
|
connect( mActionOpenProject, &QAction::triggered, this, &QgisApp::fileOpen );
|
|
connect( mActionSaveProject, &QAction::triggered, this, &QgisApp::fileSave );
|
|
connect( mActionSaveProjectAs, &QAction::triggered, this, &QgisApp::fileSaveAs );
|
|
connect( mActionSaveMapAsImage, &QAction::triggered, this, [ = ] { saveMapAsImage(); } );
|
|
connect( mActionSaveMapAsPdf, &QAction::triggered, this, [ = ] { saveMapAsPdf(); } );
|
|
connect( mActionNewMapCanvas, &QAction::triggered, this, &QgisApp::newMapCanvas );
|
|
connect( mActionNew3DMapCanvas, &QAction::triggered, this, &QgisApp::new3DMapCanvas );
|
|
connect( mActionNewPrintComposer, &QAction::triggered, this, &QgisApp::newPrintComposer );
|
|
connect( mActionShowComposerManager, &QAction::triggered, this, &QgisApp::showComposerManager );
|
|
connect( mActionExit, &QAction::triggered, this, &QgisApp::fileExit );
|
|
connect( mActionDxfExport, &QAction::triggered, this, &QgisApp::dxfExport );
|
|
connect( mActionDwgImport, &QAction::triggered, this, &QgisApp::dwgImport );
|
|
|
|
// Edit Menu Items
|
|
|
|
connect( mActionUndo, &QAction::triggered, mUndoWidget, &QgsUndoWidget::undo );
|
|
connect( mActionRedo, &QAction::triggered, mUndoWidget, &QgsUndoWidget::redo );
|
|
connect( mActionCutFeatures, &QAction::triggered, this, [ = ] { editCut(); } );
|
|
connect( mActionCopyFeatures, &QAction::triggered, this, [ = ] { editCopy(); } );
|
|
connect( mActionPasteFeatures, &QAction::triggered, this, [ = ] { editPaste(); } );
|
|
connect( mActionPasteAsNewVector, &QAction::triggered, this, &QgisApp::pasteAsNewVector );
|
|
connect( mActionPasteAsNewMemoryVector, &QAction::triggered, this, [ = ] { pasteAsNewMemoryVector(); } );
|
|
connect( mActionCopyStyle, &QAction::triggered, this, [ = ] { copyStyle(); } );
|
|
connect( mActionPasteStyle, &QAction::triggered, this, [ = ] { pasteStyle(); } );
|
|
connect( mActionAddFeature, &QAction::triggered, this, &QgisApp::addFeature );
|
|
connect( mActionCircularStringCurvePoint, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mCircularStringRadius ); } );
|
|
connect( mActionCircularStringRadius, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mCircularStringRadius ); } );
|
|
connect( mActionCircle2Points, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mCircle2Points ); } );
|
|
connect( mActionCircle3Points, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mCircle3Points ); } );
|
|
connect( mActionCircle3Tangents, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mCircle3Tangents ); } );
|
|
connect( mActionCircle2TangentsPoint, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mCircle2TangentsPoint ); } );
|
|
connect( mActionCircleCenterPoint, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mCircleCenterPoint ); } );
|
|
connect( mActionEllipseCenter2Points, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mEllipseCenter2Points ); } );
|
|
connect( mActionEllipseCenterPoint, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mEllipseCenterPoint ); } );
|
|
connect( mActionEllipseExtent, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mEllipseExtent ); } );
|
|
connect( mActionEllipseFoci, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mEllipseFoci ); } );
|
|
connect( mActionRectangleCenterPoint, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mRectangleCenterPoint ); } );
|
|
connect( mActionRectangleExtent, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mRectangleExtent ); } );
|
|
connect( mActionRectangle3Points, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mRectangle3Points ); } );
|
|
connect( mActionRegularPolygon2Points, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mRegularPolygon2Points ); } );
|
|
connect( mActionRegularPolygonCenterPoint, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mRegularPolygonCenterPoint ); } );
|
|
connect( mActionRegularPolygonCenterCorner, &QAction::triggered, this, [ = ] { setMapTool( mMapTools.mRegularPolygonCenterCorner ); } );
|
|
connect( mActionMoveFeature, &QAction::triggered, this, &QgisApp::moveFeature );
|
|
connect( mActionMoveFeature, &QAction::triggered, this, &QgisApp::moveFeature );
|
|
connect( mActionMoveFeatureCopy, &QAction::triggered, this, &QgisApp::moveFeatureCopy );
|
|
connect( mActionRotateFeature, &QAction::triggered, this, &QgisApp::rotateFeature );
|
|
|
|
connect( mActionReshapeFeatures, &QAction::triggered, this, &QgisApp::reshapeFeatures );
|
|
connect( mActionSplitFeatures, &QAction::triggered, this, &QgisApp::splitFeatures );
|
|
connect( mActionSplitParts, &QAction::triggered, this, &QgisApp::splitParts );
|
|
connect( mActionDeleteSelected, &QAction::triggered, this, [ = ] { deleteSelected(); } );
|
|
connect( mActionAddRing, &QAction::triggered, this, &QgisApp::addRing );
|
|
connect( mActionFillRing, &QAction::triggered, this, &QgisApp::fillRing );
|
|
connect( mActionAddPart, &QAction::triggered, this, &QgisApp::addPart );
|
|
connect( mActionSimplifyFeature, &QAction::triggered, this, &QgisApp::simplifyFeature );
|
|
connect( mActionDeleteRing, &QAction::triggered, this, &QgisApp::deleteRing );
|
|
connect( mActionDeletePart, &QAction::triggered, this, &QgisApp::deletePart );
|
|
connect( mActionMergeFeatures, &QAction::triggered, this, &QgisApp::mergeSelectedFeatures );
|
|
connect( mActionMergeFeatureAttributes, &QAction::triggered, this, &QgisApp::mergeAttributesOfSelectedFeatures );
|
|
connect( mActionMultiEditAttributes, &QAction::triggered, this, &QgisApp::modifyAttributesOfSelectedFeatures );
|
|
connect( mActionNodeTool, &QAction::triggered, this, &QgisApp::nodeTool );
|
|
connect( mActionRotatePointSymbols, &QAction::triggered, this, &QgisApp::rotatePointSymbols );
|
|
connect( mActionOffsetPointSymbol, &QAction::triggered, this, &QgisApp::offsetPointSymbol );
|
|
connect( mActionSnappingOptions, &QAction::triggered, this, &QgisApp::snappingOptions );
|
|
connect( mActionOffsetCurve, &QAction::triggered, this, &QgisApp::offsetCurve );
|
|
|
|
// View Menu Items
|
|
connect( mActionPan, &QAction::triggered, this, &QgisApp::pan );
|
|
connect( mActionPanToSelected, &QAction::triggered, this, &QgisApp::panToSelected );
|
|
connect( mActionZoomIn, &QAction::triggered, this, &QgisApp::zoomIn );
|
|
connect( mActionZoomOut, &QAction::triggered, this, &QgisApp::zoomOut );
|
|
connect( mActionSelectFeatures, &QAction::triggered, this, &QgisApp::selectFeatures );
|
|
connect( mActionSelectPolygon, &QAction::triggered, this, &QgisApp::selectByPolygon );
|
|
connect( mActionSelectFreehand, &QAction::triggered, this, &QgisApp::selectByFreehand );
|
|
connect( mActionSelectRadius, &QAction::triggered, this, &QgisApp::selectByRadius );
|
|
connect( mActionDeselectAll, &QAction::triggered, this, &QgisApp::deselectAll );
|
|
connect( mActionSelectAll, &QAction::triggered, this, &QgisApp::selectAll );
|
|
connect( mActionInvertSelection, &QAction::triggered, this, &QgisApp::invertSelection );
|
|
connect( mActionSelectByExpression, &QAction::triggered, this, &QgisApp::selectByExpression );
|
|
connect( mActionSelectByForm, &QAction::triggered, this, &QgisApp::selectByForm );
|
|
connect( mActionIdentify, &QAction::triggered, this, &QgisApp::identify );
|
|
connect( mActionFeatureAction, &QAction::triggered, this, &QgisApp::doFeatureAction );
|
|
connect( mActionMeasure, &QAction::triggered, this, &QgisApp::measure );
|
|
connect( mActionMeasureArea, &QAction::triggered, this, &QgisApp::measureArea );
|
|
connect( mActionMeasureAngle, &QAction::triggered, this, &QgisApp::measureAngle );
|
|
connect( mActionZoomFullExtent, &QAction::triggered, this, &QgisApp::zoomFull );
|
|
connect( mActionZoomToLayer, &QAction::triggered, this, &QgisApp::zoomToLayerExtent );
|
|
connect( mActionZoomToSelected, &QAction::triggered, this, &QgisApp::zoomToSelected );
|
|
connect( mActionZoomLast, &QAction::triggered, this, &QgisApp::zoomToPrevious );
|
|
connect( mActionZoomNext, &QAction::triggered, this, &QgisApp::zoomToNext );
|
|
connect( mActionZoomActualSize, &QAction::triggered, this, &QgisApp::zoomActualSize );
|
|
connect( mActionMapTips, &QAction::toggled, this, &QgisApp::toggleMapTips );
|
|
connect( mActionNewBookmark, &QAction::triggered, this, &QgisApp::newBookmark );
|
|
connect( mActionShowBookmarks, &QAction::triggered, this, &QgisApp::showBookmarks );
|
|
connect( mActionDraw, &QAction::triggered, this, &QgisApp::refreshMapCanvas );
|
|
connect( mActionTextAnnotation, &QAction::triggered, this, &QgisApp::addTextAnnotation );
|
|
connect( mActionFormAnnotation, &QAction::triggered, this, &QgisApp::addFormAnnotation );
|
|
connect( mActionHtmlAnnotation, &QAction::triggered, this, &QgisApp::addHtmlAnnotation );
|
|
connect( mActionSvgAnnotation, &QAction::triggered, this, &QgisApp::addSvgAnnotation );
|
|
connect( mActionAnnotation, &QAction::triggered, this, &QgisApp::modifyAnnotation );
|
|
connect( mActionLabeling, &QAction::triggered, this, &QgisApp::labeling );
|
|
connect( mActionStatisticalSummary, &QAction::triggered, this, &QgisApp::showStatisticsDockWidget );
|
|
|
|
// Layer Menu Items
|
|
|
|
connect( mActionDataSourceManager, &QAction::triggered, this, [ = ]() { dataSourceManager(); } );
|
|
connect( mActionNewVectorLayer, &QAction::triggered, this, &QgisApp::newVectorLayer );
|
|
connect( mActionNewSpatiaLiteLayer, &QAction::triggered, this, &QgisApp::newSpatialiteLayer );
|
|
connect( mActionNewGeoPackageLayer, &QAction::triggered, this, &QgisApp::newGeoPackageLayer );
|
|
connect( mActionNewMemoryLayer, &QAction::triggered, this, &QgisApp::newMemoryLayer );
|
|
connect( mActionShowRasterCalculator, &QAction::triggered, this, &QgisApp::showRasterCalculator );
|
|
connect( mActionShowAlignRasterTool, &QAction::triggered, this, &QgisApp::showAlignRasterTool );
|
|
connect( mActionEmbedLayers, &QAction::triggered, this, &QgisApp::embedLayers );
|
|
connect( mActionAddLayerDefinition, &QAction::triggered, this, &QgisApp::addLayerDefinition );
|
|
connect( mActionAddOgrLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "ogr" ) ); } );
|
|
connect( mActionAddRasterLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "gdal" ) ); } );
|
|
connect( mActionAddPgLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "postgres" ) ); } );
|
|
connect( mActionAddSpatiaLiteLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "spatialite" ) ); } );
|
|
connect( mActionAddMssqlLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "mssql" ) ); } );
|
|
connect( mActionAddDb2Layer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "DB2" ) ); } );
|
|
connect( mActionAddOracleLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "oracle" ) ); } );
|
|
connect( mActionAddWmsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "wms" ) ); } );
|
|
connect( mActionAddWcsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "wcs" ) ); } );
|
|
connect( mActionAddWfsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "WFS" ) ); } );
|
|
connect( mActionAddAfsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "arcgisfeatureserver" ) ); } );
|
|
connect( mActionAddAmsLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "arcgismapserver" ) ); } );
|
|
connect( mActionAddDelimitedText, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "delimitedtext" ) ); } );
|
|
connect( mActionAddVirtualLayer, &QAction::triggered, this, [ = ] { dataSourceManager( QStringLiteral( "virtual" ) ); } );
|
|
connect( mActionOpenTable, &QAction::triggered, this, &QgisApp::attributeTable );
|
|
connect( mActionOpenFieldCalc, &QAction::triggered, this, &QgisApp::fieldCalculator );
|
|
connect( mActionToggleEditing, &QAction::triggered, this, [ = ] { toggleEditing(); } );
|
|
connect( mActionSaveLayerEdits, &QAction::triggered, this, &QgisApp::saveActiveLayerEdits );
|
|
connect( mActionSaveEdits, &QAction::triggered, this, [ = ] { saveEdits(); } );
|
|
connect( mActionSaveAllEdits, &QAction::triggered, this, &QgisApp::saveAllEdits );
|
|
connect( mActionRollbackEdits, &QAction::triggered, this, &QgisApp::rollbackEdits );
|
|
connect( mActionRollbackAllEdits, &QAction::triggered, this, &QgisApp::rollbackAllEdits );
|
|
connect( mActionCancelEdits, &QAction::triggered, this, [ = ] { cancelEdits(); } );
|
|
connect( mActionCancelAllEdits, &QAction::triggered, this, &QgisApp::cancelAllEdits );
|
|
connect( mActionLayerSaveAs, &QAction::triggered, this, &QgisApp::saveAsFile );
|
|
connect( mActionSaveLayerDefinition, &QAction::triggered, this, &QgisApp::saveAsLayerDefinition );
|
|
connect( mActionRemoveLayer, &QAction::triggered, this, &QgisApp::removeLayer );
|
|
connect( mActionDuplicateLayer, &QAction::triggered, this, [ = ] { duplicateLayers(); } );
|
|
connect( mActionSetLayerScaleVisibility, &QAction::triggered, this, &QgisApp::setLayerScaleVisibility );
|
|
connect( mActionSetLayerCRS, &QAction::triggered, this, &QgisApp::setLayerCrs );
|
|
connect( mActionSetProjectCRSFromLayer, &QAction::triggered, this, &QgisApp::setProjectCrsFromLayer );
|
|
connect( mActionLayerProperties, &QAction::triggered, this, &QgisApp::layerProperties );
|
|
connect( mActionLayerSubsetString, &QAction::triggered, this, &QgisApp::layerSubsetString );
|
|
connect( mActionAddToOverview, &QAction::triggered, this, &QgisApp::isInOverview );
|
|
connect( mActionAddAllToOverview, &QAction::triggered, this, &QgisApp::addAllToOverview );
|
|
connect( mActionRemoveAllFromOverview, &QAction::triggered, this, &QgisApp::removeAllFromOverview );
|
|
connect( mActionShowAllLayers, &QAction::triggered, this, &QgisApp::showAllLayers );
|
|
connect( mActionHideAllLayers, &QAction::triggered, this, &QgisApp::hideAllLayers );
|
|
connect( mActionShowSelectedLayers, &QAction::triggered, this, &QgisApp::showSelectedLayers );
|
|
connect( mActionHideSelectedLayers, &QAction::triggered, this, &QgisApp::hideSelectedLayers );
|
|
connect( mActionHideDeselectedLayers, &QAction::triggered, this, &QgisApp::hideDeselectedLayers );
|
|
|
|
// Plugin Menu Items
|
|
|
|
connect( mActionManagePlugins, &QAction::triggered, this, &QgisApp::showPluginManager );
|
|
connect( mActionInstallFromZip, &QAction::triggered, this, &QgisApp::installPluginFromZip );
|
|
connect( mActionShowPythonDialog, &QAction::triggered, this, &QgisApp::showPythonDialog );
|
|
|
|
// Settings Menu Items
|
|
|
|
connect( mActionToggleFullScreen, &QAction::triggered, this, &QgisApp::toggleFullScreen );
|
|
connect( mActionTogglePanelsVisibility, &QAction::triggered, this, &QgisApp::togglePanelsVisibility );
|
|
connect( mActionProjectProperties, &QAction::triggered, this, &QgisApp::projectProperties );
|
|
connect( mActionOptions, &QAction::triggered, this, &QgisApp::options );
|
|
connect( mActionCustomProjection, &QAction::triggered, this, &QgisApp::customProjection );
|
|
connect( mActionConfigureShortcuts, &QAction::triggered, this, &QgisApp::configureShortcuts );
|
|
connect( mActionStyleManager, &QAction::triggered, this, &QgisApp::showStyleManager );
|
|
connect( mActionCustomization, &QAction::triggered, this, &QgisApp::customize );
|
|
|
|
#ifdef Q_OS_MAC
|
|
// Window Menu Items
|
|
|
|
mActionWindowMinimize = new QAction( tr( "Minimize" ), this );
|
|
mActionWindowMinimize->setShortcut( tr( "Ctrl+M", "Minimize Window" ) );
|
|
mActionWindowMinimize->setStatusTip( tr( "Minimizes the active window to the dock" ) );
|
|
connect( mActionWindowMinimize, SIGNAL( triggered() ), this, SLOT( showActiveWindowMinimized() ) );
|
|
|
|
mActionWindowZoom = new QAction( tr( "Zoom" ), this );
|
|
mActionWindowZoom->setStatusTip( tr( "Toggles between a predefined size and the window size set by the user" ) );
|
|
connect( mActionWindowZoom, SIGNAL( triggered() ), this, SLOT( toggleActiveWindowMaximized() ) );
|
|
|
|
mActionWindowAllToFront = new QAction( tr( "Bring All to Front" ), this );
|
|
mActionWindowAllToFront->setStatusTip( tr( "Bring forward all open windows" ) );
|
|
connect( mActionWindowAllToFront, SIGNAL( triggered() ), this, SLOT( bringAllToFront() ) );
|
|
|
|
// list of open windows
|
|
mWindowActions = new QActionGroup( this );
|
|
#endif
|
|
|
|
// Vector edits menu
|
|
QMenu *menuAllEdits = new QMenu( tr( "Current Edits" ), this );
|
|
menuAllEdits->addAction( mActionSaveEdits );
|
|
menuAllEdits->addAction( mActionRollbackEdits );
|
|
menuAllEdits->addAction( mActionCancelEdits );
|
|
menuAllEdits->addSeparator();
|
|
menuAllEdits->addAction( mActionSaveAllEdits );
|
|
menuAllEdits->addAction( mActionRollbackAllEdits );
|
|
menuAllEdits->addAction( mActionCancelAllEdits );
|
|
mActionAllEdits->setMenu( menuAllEdits );
|
|
|
|
// Raster toolbar items
|
|
connect( mActionLocalHistogramStretch, &QAction::triggered, this, &QgisApp::localHistogramStretch );
|
|
connect( mActionFullHistogramStretch, &QAction::triggered, this, &QgisApp::fullHistogramStretch );
|
|
connect( mActionLocalCumulativeCutStretch, &QAction::triggered, this, &QgisApp::localCumulativeCutStretch );
|
|
connect( mActionFullCumulativeCutStretch, &QAction::triggered, this, &QgisApp::fullCumulativeCutStretch );
|
|
connect( mActionIncreaseBrightness, &QAction::triggered, this, &QgisApp::increaseBrightness );
|
|
connect( mActionDecreaseBrightness, &QAction::triggered, this, &QgisApp::decreaseBrightness );
|
|
connect( mActionIncreaseContrast, &QAction::triggered, this, &QgisApp::increaseContrast );
|
|
connect( mActionDecreaseContrast, &QAction::triggered, this, &QgisApp::decreaseContrast );
|
|
|
|
// Vector Menu Items
|
|
connect( mActionOSMDownload, &QAction::triggered, this, &QgisApp::osmDownloadDialog );
|
|
connect( mActionOSMImport, &QAction::triggered, this, &QgisApp::osmImportDialog );
|
|
connect( mActionOSMExport, &QAction::triggered, this, &QgisApp::osmExportDialog );
|
|
|
|
// Help Menu Items
|
|
|
|
#ifdef Q_OS_MAC
|
|
mActionHelpContents->setShortcut( QString( "Ctrl+?" ) );
|
|
mActionQgisHomePage->setShortcut( QString() );
|
|
mActionReportaBug->setShortcut( QString() );
|
|
#endif
|
|
|
|
mActionHelpContents->setEnabled( QFileInfo::exists( QgsApplication::pkgDataPath() + "/doc/index.html" ) );
|
|
|
|
connect( mActionHelpContents, &QAction::triggered, this, &QgisApp::helpContents );
|
|
connect( mActionHelpAPI, &QAction::triggered, this, &QgisApp::apiDocumentation );
|
|
connect( mActionReportaBug, &QAction::triggered, this, &QgisApp::reportaBug );
|
|
connect( mActionNeedSupport, &QAction::triggered, this, &QgisApp::supportProviders );
|
|
connect( mActionQgisHomePage, &QAction::triggered, this, &QgisApp::helpQgisHomePage );
|
|
connect( mActionCheckQgisVersion, &QAction::triggered, this, &QgisApp::checkQgisVersion );
|
|
connect( mActionAbout, &QAction::triggered, this, &QgisApp::about );
|
|
connect( mActionSponsors, &QAction::triggered, this, &QgisApp::sponsors );
|
|
|
|
connect( mActionShowPinnedLabels, &QAction::toggled, this, &QgisApp::showPinnedLabels );
|
|
connect( mActionPinLabels, &QAction::triggered, this, &QgisApp::pinLabels );
|
|
connect( mActionShowHideLabels, &QAction::triggered, this, &QgisApp::showHideLabels );
|
|
connect( mActionMoveLabel, &QAction::triggered, this, &QgisApp::moveLabel );
|
|
connect( mActionRotateLabel, &QAction::triggered, this, &QgisApp::rotateLabel );
|
|
connect( mActionChangeLabelProperties, &QAction::triggered, this, &QgisApp::changeLabelProperties );
|
|
|
|
connect( mActionDiagramProperties, &QAction::triggered, this, &QgisApp::diagramProperties );
|
|
|
|
#ifndef HAVE_POSTGRESQL
|
|
delete mActionAddPgLayer;
|
|
mActionAddPgLayer = 0;
|
|
#endif
|
|
|
|
#ifndef HAVE_ORACLE
|
|
delete mActionAddOracleLayer;
|
|
mActionAddOracleLayer = nullptr;
|
|
#endif
|
|
|
|
}
|
|
|
|
#include "qgsstyle.h"
|
|
#include "qgsstylemanagerdialog.h"
|
|
|
|
void QgisApp::showStyleManager()
|
|
{
|
|
QgsStyleManagerDialog dlg( QgsStyle::defaultStyle(), this );
|
|
dlg.exec();
|
|
}
|
|
|
|
void QgisApp::showPythonDialog()
|
|
{
|
|
#ifdef WITH_BINDINGS
|
|
if ( !mPythonUtils || !mPythonUtils->isEnabled() )
|
|
return;
|
|
|
|
bool res = mPythonUtils->runString(
|
|
"import console\n"
|
|
"console.show_console()\n", tr( "Failed to open Python console:" ), false );
|
|
|
|
if ( !res )
|
|
{
|
|
QString className, text;
|
|
mPythonUtils->getError( className, text );
|
|
messageBar()->pushMessage( tr( "Error" ), tr( "Failed to open Python console:" ) + '\n' + className + ": " + text, QgsMessageBar::WARNING );
|
|
}
|
|
#ifdef Q_OS_MAC
|
|
else
|
|
{
|
|
addWindow( mActionShowPythonDialog );
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::createActionGroups()
|
|
{
|
|
//
|
|
// Map Tool Group
|
|
mMapToolGroup = new QActionGroup( this );
|
|
mMapToolGroup->addAction( mActionPan );
|
|
mMapToolGroup->addAction( mActionZoomIn );
|
|
mMapToolGroup->addAction( mActionZoomOut );
|
|
mMapToolGroup->addAction( mActionIdentify );
|
|
mMapToolGroup->addAction( mActionFeatureAction );
|
|
mMapToolGroup->addAction( mActionSelectFeatures );
|
|
mMapToolGroup->addAction( mActionSelectPolygon );
|
|
mMapToolGroup->addAction( mActionSelectFreehand );
|
|
mMapToolGroup->addAction( mActionSelectRadius );
|
|
mMapToolGroup->addAction( mActionDeselectAll );
|
|
mMapToolGroup->addAction( mActionSelectAll );
|
|
mMapToolGroup->addAction( mActionInvertSelection );
|
|
mMapToolGroup->addAction( mActionMeasure );
|
|
mMapToolGroup->addAction( mActionMeasureArea );
|
|
mMapToolGroup->addAction( mActionMeasureAngle );
|
|
mMapToolGroup->addAction( mActionAddFeature );
|
|
mMapToolGroup->addAction( mActionCircularStringCurvePoint );
|
|
mMapToolGroup->addAction( mActionCircularStringRadius );
|
|
mMapToolGroup->addAction( mActionCircle2Points );
|
|
mMapToolGroup->addAction( mActionCircle3Points );
|
|
mMapToolGroup->addAction( mActionCircle3Tangents );
|
|
mMapToolGroup->addAction( mActionCircle2TangentsPoint );
|
|
mMapToolGroup->addAction( mActionCircleCenterPoint );
|
|
mMapToolGroup->addAction( mActionEllipseCenter2Points );
|
|
mMapToolGroup->addAction( mActionEllipseCenterPoint );
|
|
mMapToolGroup->addAction( mActionEllipseExtent );
|
|
mMapToolGroup->addAction( mActionEllipseFoci );
|
|
mMapToolGroup->addAction( mActionRectangleCenterPoint );
|
|
mMapToolGroup->addAction( mActionRectangleExtent );
|
|
mMapToolGroup->addAction( mActionRectangle3Points );
|
|
mMapToolGroup->addAction( mActionRegularPolygon2Points );
|
|
mMapToolGroup->addAction( mActionRegularPolygonCenterPoint );
|
|
mMapToolGroup->addAction( mActionRegularPolygonCenterCorner );
|
|
mMapToolGroup->addAction( mActionMoveFeature );
|
|
mMapToolGroup->addAction( mActionMoveFeatureCopy );
|
|
mMapToolGroup->addAction( mActionRotateFeature );
|
|
mMapToolGroup->addAction( mActionOffsetCurve );
|
|
mMapToolGroup->addAction( mActionReshapeFeatures );
|
|
mMapToolGroup->addAction( mActionSplitFeatures );
|
|
mMapToolGroup->addAction( mActionSplitParts );
|
|
mMapToolGroup->addAction( mActionDeleteSelected );
|
|
mMapToolGroup->addAction( mActionAddRing );
|
|
mMapToolGroup->addAction( mActionFillRing );
|
|
mMapToolGroup->addAction( mActionAddPart );
|
|
mMapToolGroup->addAction( mActionSimplifyFeature );
|
|
mMapToolGroup->addAction( mActionDeleteRing );
|
|
mMapToolGroup->addAction( mActionDeletePart );
|
|
mMapToolGroup->addAction( mActionMergeFeatures );
|
|
mMapToolGroup->addAction( mActionMergeFeatureAttributes );
|
|
mMapToolGroup->addAction( mActionNodeTool );
|
|
mMapToolGroup->addAction( mActionRotatePointSymbols );
|
|
mMapToolGroup->addAction( mActionOffsetPointSymbol );
|
|
mMapToolGroup->addAction( mActionPinLabels );
|
|
mMapToolGroup->addAction( mActionShowHideLabels );
|
|
mMapToolGroup->addAction( mActionMoveLabel );
|
|
mMapToolGroup->addAction( mActionRotateLabel );
|
|
mMapToolGroup->addAction( mActionChangeLabelProperties );
|
|
|
|
//
|
|
// Preview Modes Group
|
|
QActionGroup *mPreviewGroup = new QActionGroup( this );
|
|
mPreviewGroup->setExclusive( true );
|
|
mActionPreviewModeOff->setActionGroup( mPreviewGroup );
|
|
mActionPreviewModeGrayscale->setActionGroup( mPreviewGroup );
|
|
mActionPreviewModeMono->setActionGroup( mPreviewGroup );
|
|
mActionPreviewProtanope->setActionGroup( mPreviewGroup );
|
|
mActionPreviewDeuteranope->setActionGroup( mPreviewGroup );
|
|
}
|
|
|
|
void QgisApp::setAppStyleSheet( const QString &stylesheet )
|
|
{
|
|
setStyleSheet( stylesheet );
|
|
|
|
// cascade styles to any current project composers
|
|
Q_FOREACH ( QgsComposer *c, mPrintComposers )
|
|
{
|
|
c->setStyleSheet( stylesheet );
|
|
}
|
|
Q_FOREACH ( QgsLayoutDesignerDialog *d, mLayoutDesignerDialogs )
|
|
{
|
|
d->setStyleSheet( stylesheet );
|
|
}
|
|
}
|
|
|
|
int QgisApp::messageTimeout()
|
|
{
|
|
QgsSettings settings;
|
|
return settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
|
|
}
|
|
|
|
void QgisApp::createMenus()
|
|
{
|
|
/*
|
|
* The User Interface Guidelines for each platform specify different locations
|
|
* for the following items.
|
|
*
|
|
* Project Properties:
|
|
* Gnome, Mac, Win - File/Project menu above print commands (Win doesn't specify)
|
|
* Kde - Settings menu
|
|
*
|
|
* Custom CRS, Options:
|
|
* Gnome - bottom of Edit menu
|
|
* Mac - Application menu (moved automatically by Qt)
|
|
* Kde, Win - Settings menu (Win should use Tools menu but we don't have one)
|
|
*
|
|
* Panel and Toolbar submenus, Toggle Full Screen:
|
|
* Gnome, Mac, Win - View menu
|
|
* Kde - Settings menu
|
|
*
|
|
* For Mac, About and Exit are also automatically moved by Qt to the Application menu.
|
|
*/
|
|
|
|
// Layer menu
|
|
|
|
// Panel and Toolbar Submenus
|
|
mPanelMenu = new QMenu( tr( "Panels" ), this );
|
|
mPanelMenu->setObjectName( QStringLiteral( "mPanelMenu" ) );
|
|
mToolbarMenu = new QMenu( tr( "Toolbars" ), this );
|
|
mToolbarMenu->setObjectName( QStringLiteral( "mToolbarMenu" ) );
|
|
|
|
// Get platform for menu layout customization (Gnome, Kde, Mac, Win)
|
|
QDialogButtonBox::ButtonLayout layout =
|
|
QDialogButtonBox::ButtonLayout( style()->styleHint( QStyle::SH_DialogButtonLayout, nullptr, this ) );
|
|
|
|
// Project Menu
|
|
|
|
// Connect once for the entire submenu.
|
|
connect( mRecentProjectsMenu, &QMenu::triggered, this, static_cast < void ( QgisApp::* )( QAction *action ) >( &QgisApp::openProject ) );
|
|
connect( mProjectFromTemplateMenu, &QMenu::triggered,
|
|
this, &QgisApp::fileNewFromTemplateAction );
|
|
|
|
if ( layout == QDialogButtonBox::GnomeLayout || layout == QDialogButtonBox::MacLayout || layout == QDialogButtonBox::WinLayout )
|
|
{
|
|
QAction *before = mActionNewPrintComposer;
|
|
mSettingsMenu->removeAction( mActionProjectProperties );
|
|
mProjectMenu->insertAction( before, mActionProjectProperties );
|
|
mProjectMenu->insertSeparator( before );
|
|
}
|
|
|
|
// View Menu
|
|
|
|
if ( layout != QDialogButtonBox::KdeLayout )
|
|
{
|
|
mViewMenu->addSeparator();
|
|
mViewMenu->addMenu( mPanelMenu );
|
|
mViewMenu->addMenu( mToolbarMenu );
|
|
mViewMenu->addAction( mActionToggleFullScreen );
|
|
mViewMenu->addAction( mActionTogglePanelsVisibility );
|
|
}
|
|
else
|
|
{
|
|
// on the top of the settings menu
|
|
QAction *before = mActionProjectProperties;
|
|
mSettingsMenu->insertMenu( before, mPanelMenu );
|
|
mSettingsMenu->insertMenu( before, mToolbarMenu );
|
|
mSettingsMenu->insertAction( before, mActionToggleFullScreen );
|
|
mSettingsMenu->insertAction( before, mActionTogglePanelsVisibility );
|
|
mSettingsMenu->insertSeparator( before );
|
|
}
|
|
|
|
#ifdef Q_OS_MAC
|
|
|
|
// keep plugins from hijacking About and Preferences application menus
|
|
// these duplicate actions will be moved to application menus by Qt
|
|
mProjectMenu->addAction( mActionAbout );
|
|
QAction *actionPrefs = new QAction( tr( "Preferences..." ), this );
|
|
actionPrefs->setMenuRole( QAction::PreferencesRole );
|
|
actionPrefs->setIcon( mActionOptions->icon() );
|
|
connect( actionPrefs, &QAction::triggered, this, &QgisApp::options );
|
|
mProjectMenu->addAction( actionPrefs );
|
|
|
|
// Window Menu
|
|
|
|
mWindowMenu = new QMenu( tr( "Window" ), this );
|
|
|
|
mWindowMenu->addAction( mActionWindowMinimize );
|
|
mWindowMenu->addAction( mActionWindowZoom );
|
|
mWindowMenu->addSeparator();
|
|
|
|
mWindowMenu->addAction( mActionWindowAllToFront );
|
|
mWindowMenu->addSeparator();
|
|
|
|
// insert before Help menu, as per Mac OS convention
|
|
menuBar()->insertMenu( mHelpMenu->menuAction(), mWindowMenu );
|
|
#endif
|
|
|
|
// Database Menu
|
|
// don't add it yet, wait for a plugin
|
|
mDatabaseMenu = new QMenu( tr( "&Database" ), menuBar() );
|
|
mDatabaseMenu->setObjectName( QStringLiteral( "mDatabaseMenu" ) );
|
|
// Web Menu
|
|
// don't add it yet, wait for a plugin
|
|
mWebMenu = new QMenu( tr( "&Web" ), menuBar() );
|
|
mWebMenu->setObjectName( QStringLiteral( "mWebMenu" ) );
|
|
|
|
createProfileMenu();
|
|
}
|
|
|
|
void QgisApp::refreshProfileMenu()
|
|
{
|
|
mConfigMenu->clear();
|
|
QgsUserProfile *profile = userProfileManager()->userProfile();
|
|
QString activeName = profile->name();
|
|
mConfigMenu->setTitle( tr( "&User Profiles" ) );
|
|
|
|
QActionGroup *profileGroup = new QActionGroup( mConfigMenu );
|
|
profileGroup->setExclusive( true );
|
|
|
|
Q_FOREACH ( const QString &name, userProfileManager()->allProfiles() )
|
|
{
|
|
std::unique_ptr< QgsUserProfile > namedProfile( userProfileManager()->profileForName( name ) );
|
|
QAction *action = new QAction( namedProfile->icon(), namedProfile->alias(), mConfigMenu );
|
|
action->setToolTip( namedProfile->folder() );
|
|
action->setCheckable( true );
|
|
profileGroup->addAction( action );
|
|
mConfigMenu->addAction( action );
|
|
|
|
if ( name == activeName )
|
|
{
|
|
action->setChecked( true );
|
|
}
|
|
else
|
|
{
|
|
connect( action, &QAction::triggered, this, [this, name]()
|
|
{
|
|
userProfileManager()->loadUserProfile( name );
|
|
} );
|
|
}
|
|
}
|
|
|
|
mConfigMenu->addSeparator( );
|
|
|
|
QAction *openProfileFolderAction = mConfigMenu->addAction( tr( "Open active profile folder" ) );
|
|
connect( openProfileFolderAction, &QAction::triggered, this, [this]()
|
|
{
|
|
QDesktopServices::openUrl( QUrl::fromLocalFile( userProfileManager()->userProfile()->folder() ) );
|
|
} );
|
|
|
|
QAction *newProfileAction = mConfigMenu->addAction( tr( "New profile" ) );
|
|
connect( newProfileAction, &QAction::triggered, this, &QgisApp::newProfile );
|
|
}
|
|
|
|
void QgisApp::createProfileMenu()
|
|
{
|
|
mConfigMenu = new QMenu();
|
|
|
|
settingsMenu()->insertMenu( settingsMenu()->actions().first(), mConfigMenu );
|
|
|
|
refreshProfileMenu();
|
|
}
|
|
|
|
void QgisApp::createToolBars()
|
|
{
|
|
QgsSettings settings;
|
|
// QSize myIconSize ( 32,32 ); //large icons
|
|
// Note: we need to set each object name to ensure that
|
|
// qmainwindow::saveState and qmainwindow::restoreState
|
|
// work properly
|
|
|
|
QList<QToolBar *> toolbarMenuToolBars;
|
|
toolbarMenuToolBars << mFileToolBar
|
|
<< mDataSourceManagerToolBar
|
|
<< mLayerToolBar
|
|
<< mDigitizeToolBar
|
|
<< mAdvancedDigitizeToolBar
|
|
<< mMapNavToolBar
|
|
<< mAttributesToolBar
|
|
<< mPluginToolBar
|
|
<< mHelpToolBar
|
|
<< mRasterToolBar
|
|
<< mVectorToolBar
|
|
<< mDatabaseToolBar
|
|
<< mWebToolBar
|
|
<< mLabelToolBar;
|
|
|
|
|
|
mSnappingWidget = new QgsSnappingWidget( QgsProject::instance(), mMapCanvas, mSnappingToolBar );
|
|
mSnappingWidget->setObjectName( QStringLiteral( "mSnappingWidget" ) );
|
|
connect( mSnappingWidget, &QgsSnappingWidget::snappingConfigChanged, QgsProject::instance(), [ = ] { QgsProject::instance()->setSnappingConfig( mSnappingWidget->config() ); } );
|
|
mSnappingToolBar->addWidget( mSnappingWidget );
|
|
|
|
mTracer = new QgsMapCanvasTracer( mMapCanvas, messageBar() );
|
|
mTracer->setActionEnableTracing( mSnappingWidget->enableTracingAction() );
|
|
|
|
QList<QAction *> toolbarMenuActions;
|
|
// Set action names so that they can be used in customization
|
|
Q_FOREACH ( QToolBar *toolBar, toolbarMenuToolBars )
|
|
{
|
|
toolBar->toggleViewAction()->setObjectName( "mActionToggle" + toolBar->objectName().mid( 1 ) );
|
|
toolbarMenuActions << toolBar->toggleViewAction();
|
|
}
|
|
|
|
// sort actions in toolbar menu
|
|
std::sort( toolbarMenuActions.begin(), toolbarMenuActions.end(), cmpByText_ );
|
|
|
|
mToolbarMenu->addActions( toolbarMenuActions );
|
|
|
|
// selection tool button
|
|
|
|
QToolButton *bt = new QToolButton( mAttributesToolBar );
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
QList<QAction *> selectActions;
|
|
selectActions << mActionSelectByForm << mActionSelectByExpression << mActionSelectAll
|
|
<< mActionInvertSelection;
|
|
bt->addActions( selectActions );
|
|
bt->setDefaultAction( mActionSelectByForm );
|
|
QAction *selectionAction = mAttributesToolBar->insertWidget( mActionDeselectAll, bt );
|
|
selectionAction->setObjectName( QStringLiteral( "ActionSelection" ) );
|
|
|
|
// select tool button
|
|
|
|
bt = new QToolButton( mAttributesToolBar );
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
QList<QAction *> selectionActions;
|
|
selectionActions << mActionSelectFeatures << mActionSelectPolygon
|
|
<< mActionSelectFreehand << mActionSelectRadius;
|
|
bt->addActions( selectionActions );
|
|
|
|
QAction *defSelectAction = mActionSelectFeatures;
|
|
switch ( settings.value( QStringLiteral( "UI/selectTool" ), 0 ).toInt() )
|
|
{
|
|
case 0:
|
|
defSelectAction = mActionSelectFeatures;
|
|
break;
|
|
case 1:
|
|
defSelectAction = mActionSelectFeatures;
|
|
break;
|
|
case 2:
|
|
defSelectAction = mActionSelectRadius;
|
|
break;
|
|
case 3:
|
|
defSelectAction = mActionSelectPolygon;
|
|
break;
|
|
case 4:
|
|
defSelectAction = mActionSelectFreehand;
|
|
break;
|
|
}
|
|
bt->setDefaultAction( defSelectAction );
|
|
QAction *selectAction = mAttributesToolBar->insertWidget( selectionAction, bt );
|
|
selectAction->setObjectName( QStringLiteral( "ActionSelect" ) );
|
|
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
|
|
// feature action tool button
|
|
|
|
bt = new QToolButton( mAttributesToolBar );
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
bt->setDefaultAction( mActionFeatureAction );
|
|
mFeatureActionMenu = new QMenu( bt );
|
|
connect( mFeatureActionMenu, &QMenu::triggered, this, &QgisApp::updateDefaultFeatureAction );
|
|
connect( mFeatureActionMenu, &QMenu::triggered, this, &QgisApp::doFeatureAction );
|
|
connect( mFeatureActionMenu, &QMenu::aboutToShow, this, &QgisApp::refreshFeatureActions );
|
|
bt->setMenu( mFeatureActionMenu );
|
|
QAction *featureActionAction = mAttributesToolBar->insertWidget( selectAction, bt );
|
|
featureActionAction->setObjectName( QStringLiteral( "ActionFeatureAction" ) );
|
|
|
|
// measure tool button
|
|
|
|
bt = new QToolButton( mAttributesToolBar );
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
bt->addAction( mActionMeasure );
|
|
bt->addAction( mActionMeasureArea );
|
|
bt->addAction( mActionMeasureAngle );
|
|
|
|
QAction *defMeasureAction = mActionMeasure;
|
|
switch ( settings.value( QStringLiteral( "UI/measureTool" ), 0 ).toInt() )
|
|
{
|
|
case 0:
|
|
defMeasureAction = mActionMeasure;
|
|
break;
|
|
case 1:
|
|
defMeasureAction = mActionMeasureArea;
|
|
break;
|
|
case 2:
|
|
defMeasureAction = mActionMeasureAngle;
|
|
break;
|
|
}
|
|
bt->setDefaultAction( defMeasureAction );
|
|
QAction *measureAction = mAttributesToolBar->insertWidget( mActionMapTips, bt );
|
|
measureAction->setObjectName( QStringLiteral( "ActionMeasure" ) );
|
|
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
|
|
// annotation tool button
|
|
|
|
bt = new QToolButton();
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
bt->addAction( mActionTextAnnotation );
|
|
bt->addAction( mActionFormAnnotation );
|
|
bt->addAction( mActionHtmlAnnotation );
|
|
bt->addAction( mActionSvgAnnotation );
|
|
bt->addAction( mActionAnnotation );
|
|
|
|
QAction *defAnnotationAction = mActionTextAnnotation;
|
|
switch ( settings.value( QStringLiteral( "UI/annotationTool" ), 0 ).toInt() )
|
|
{
|
|
case 0:
|
|
defAnnotationAction = mActionTextAnnotation;
|
|
break;
|
|
case 1:
|
|
defAnnotationAction = mActionFormAnnotation;
|
|
break;
|
|
case 2:
|
|
defAnnotationAction = mActionHtmlAnnotation;
|
|
break;
|
|
case 3:
|
|
defAnnotationAction = mActionSvgAnnotation;
|
|
break;
|
|
case 4:
|
|
defAnnotationAction = mActionAnnotation;
|
|
break;
|
|
|
|
}
|
|
bt->setDefaultAction( defAnnotationAction );
|
|
QAction *annotationAction = mAttributesToolBar->addWidget( bt );
|
|
annotationAction->setObjectName( QStringLiteral( "ActionAnnotation" ) );
|
|
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
|
|
// vector layer edits tool buttons
|
|
QToolButton *tbAllEdits = qobject_cast<QToolButton *>( mDigitizeToolBar->widgetForAction( mActionAllEdits ) );
|
|
tbAllEdits->setPopupMode( QToolButton::InstantPopup );
|
|
|
|
// new layer tool button
|
|
|
|
bt = new QToolButton();
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
bt->addAction( mActionNewVectorLayer );
|
|
bt->addAction( mActionNewSpatiaLiteLayer );
|
|
bt->addAction( mActionNewGeoPackageLayer );
|
|
bt->addAction( mActionNewMemoryLayer );
|
|
|
|
QAction *defNewLayerAction = mActionNewVectorLayer;
|
|
switch ( settings.value( QStringLiteral( "UI/defaultNewLayer" ), 1 ).toInt() )
|
|
{
|
|
case 0:
|
|
defNewLayerAction = mActionNewSpatiaLiteLayer;
|
|
break;
|
|
case 1:
|
|
defNewLayerAction = mActionNewVectorLayer;
|
|
break;
|
|
case 2:
|
|
defNewLayerAction = mActionNewMemoryLayer;
|
|
break;
|
|
case 3:
|
|
defNewLayerAction = mActionNewGeoPackageLayer;
|
|
break;
|
|
}
|
|
bt->setDefaultAction( defNewLayerAction );
|
|
QAction *newLayerAction = mLayerToolBar->addWidget( bt );
|
|
|
|
newLayerAction->setObjectName( QStringLiteral( "ActionNewLayer" ) );
|
|
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
|
|
// map service tool button
|
|
bt = new QToolButton();
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
bt->addAction( mActionAddWmsLayer );
|
|
bt->addAction( mActionAddAmsLayer );
|
|
QAction *defMapServiceAction = mActionAddWmsLayer;
|
|
switch ( settings.value( QStringLiteral( "UI/defaultMapService" ), 0 ).toInt() )
|
|
{
|
|
case 0:
|
|
defMapServiceAction = mActionAddWmsLayer;
|
|
break;
|
|
case 1:
|
|
defMapServiceAction = mActionAddAmsLayer;
|
|
break;
|
|
};
|
|
bt->setDefaultAction( defMapServiceAction );
|
|
QAction *mapServiceAction = mLayerToolBar->insertWidget( mActionAddWmsLayer, bt );
|
|
mLayerToolBar->removeAction( mActionAddWmsLayer );
|
|
mapServiceAction->setObjectName( QStringLiteral( "ActionMapService" ) );
|
|
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
|
|
// feature service tool button
|
|
bt = new QToolButton();
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
bt->addAction( mActionAddWfsLayer );
|
|
bt->addAction( mActionAddAfsLayer );
|
|
QAction *defFeatureServiceAction = mActionAddWfsLayer;
|
|
switch ( settings.value( QStringLiteral( "UI/defaultFeatureService" ), 0 ).toInt() )
|
|
{
|
|
case 0:
|
|
defFeatureServiceAction = mActionAddWfsLayer;
|
|
break;
|
|
case 1:
|
|
defFeatureServiceAction = mActionAddAfsLayer;
|
|
break;
|
|
};
|
|
bt->setDefaultAction( defFeatureServiceAction );
|
|
QAction *featureServiceAction = mLayerToolBar->insertWidget( mActionAddWfsLayer, bt );
|
|
mLayerToolBar->removeAction( mActionAddWfsLayer );
|
|
featureServiceAction->setObjectName( QStringLiteral( "ActionFeatureService" ) );
|
|
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
|
|
// add db layer button
|
|
bt = new QToolButton();
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
if ( mActionAddPgLayer )
|
|
bt->addAction( mActionAddPgLayer );
|
|
if ( mActionAddMssqlLayer )
|
|
bt->addAction( mActionAddMssqlLayer );
|
|
if ( mActionAddDb2Layer )
|
|
bt->addAction( mActionAddDb2Layer );
|
|
if ( mActionAddOracleLayer )
|
|
bt->addAction( mActionAddOracleLayer );
|
|
QAction *defAddDbLayerAction = mActionAddPgLayer;
|
|
switch ( settings.value( QStringLiteral( "UI/defaultAddDbLayerAction" ), 0 ).toInt() )
|
|
{
|
|
case 0:
|
|
defAddDbLayerAction = mActionAddPgLayer;
|
|
break;
|
|
case 1:
|
|
defAddDbLayerAction = mActionAddMssqlLayer;
|
|
break;
|
|
case 2:
|
|
defAddDbLayerAction = mActionAddDb2Layer;
|
|
break;
|
|
case 3:
|
|
defAddDbLayerAction = mActionAddOracleLayer;
|
|
break;
|
|
}
|
|
if ( defAddDbLayerAction )
|
|
bt->setDefaultAction( defAddDbLayerAction );
|
|
QAction *addDbLayerAction = mLayerToolBar->insertWidget( mapServiceAction, bt );
|
|
addDbLayerAction->setObjectName( QStringLiteral( "ActionAddDbLayer" ) );
|
|
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
|
|
QLayout *layout = mLayerToolBar->layout();
|
|
for ( int i = 0; i < layout->count(); ++i )
|
|
{
|
|
layout->itemAt( i )->setAlignment( Qt::AlignLeft );
|
|
}
|
|
|
|
//circular string digitize tool button
|
|
QToolButton *tbAddCircularString = new QToolButton( mDigitizeToolBar );
|
|
tbAddCircularString->setPopupMode( QToolButton::MenuButtonPopup );
|
|
tbAddCircularString->addAction( mActionCircularStringCurvePoint );
|
|
tbAddCircularString->addAction( mActionCircularStringRadius );
|
|
tbAddCircularString->setDefaultAction( mActionCircularStringCurvePoint );
|
|
connect( tbAddCircularString, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
mDigitizeToolBar->insertWidget( mActionNodeTool, tbAddCircularString );
|
|
|
|
//circle digitize tool button
|
|
QToolButton *tbAddCircle = new QToolButton( mRegularShapeDigitizeToolBar );
|
|
tbAddCircle->setPopupMode( QToolButton::MenuButtonPopup );
|
|
tbAddCircle->addAction( mActionCircle2Points );
|
|
tbAddCircle->addAction( mActionCircle3Points );
|
|
tbAddCircle->addAction( mActionCircle3Tangents );
|
|
tbAddCircle->addAction( mActionCircle2TangentsPoint );
|
|
tbAddCircle->addAction( mActionCircleCenterPoint );
|
|
tbAddCircle->setDefaultAction( mActionCircle2Points );
|
|
connect( tbAddCircle, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
mRegularShapeDigitizeToolBar->insertWidget( mActionNodeTool, tbAddCircle );
|
|
|
|
//ellipse digitize tool button
|
|
QToolButton *tbAddEllipse = new QToolButton( mRegularShapeDigitizeToolBar );
|
|
tbAddEllipse->setPopupMode( QToolButton::MenuButtonPopup );
|
|
tbAddEllipse->addAction( mActionEllipseCenter2Points );
|
|
tbAddEllipse->addAction( mActionEllipseCenterPoint );
|
|
tbAddEllipse->addAction( mActionEllipseExtent );
|
|
tbAddEllipse->addAction( mActionEllipseFoci );
|
|
tbAddEllipse->setDefaultAction( mActionEllipseCenter2Points );
|
|
connect( tbAddEllipse, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
mRegularShapeDigitizeToolBar->insertWidget( mActionNodeTool, tbAddEllipse );
|
|
|
|
//Rectangle digitize tool button
|
|
QToolButton *tbAddRectangle = new QToolButton( mRegularShapeDigitizeToolBar );
|
|
tbAddRectangle->setPopupMode( QToolButton::MenuButtonPopup );
|
|
tbAddRectangle->addAction( mActionRectangleCenterPoint );
|
|
tbAddRectangle->addAction( mActionRectangleExtent );
|
|
tbAddRectangle->addAction( mActionRectangle3Points );
|
|
tbAddRectangle->setDefaultAction( mActionRectangleCenterPoint );
|
|
connect( tbAddRectangle, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
mRegularShapeDigitizeToolBar->insertWidget( mActionNodeTool, tbAddRectangle );
|
|
|
|
//Regular polygon digitize tool button
|
|
QToolButton *tbAddRegularPolygon = new QToolButton( mRegularShapeDigitizeToolBar );
|
|
tbAddRegularPolygon->setPopupMode( QToolButton::MenuButtonPopup );
|
|
tbAddRegularPolygon->addAction( mActionRegularPolygon2Points );
|
|
tbAddRegularPolygon->addAction( mActionRegularPolygonCenterPoint );
|
|
tbAddRegularPolygon->addAction( mActionRegularPolygonCenterCorner );
|
|
tbAddRegularPolygon->setDefaultAction( mActionRegularPolygon2Points );
|
|
connect( tbAddRegularPolygon, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
mRegularShapeDigitizeToolBar->insertWidget( mActionNodeTool, tbAddRegularPolygon );
|
|
|
|
// move feature tool button
|
|
QToolButton *moveFeatureButton = new QToolButton( mDigitizeToolBar );
|
|
moveFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
|
|
moveFeatureButton->addAction( mActionMoveFeature );
|
|
moveFeatureButton->addAction( mActionMoveFeatureCopy );
|
|
QAction *defAction = mActionMoveFeature;
|
|
switch ( settings.value( QStringLiteral( "UI/defaultMoveTool" ), 0 ).toInt() )
|
|
{
|
|
case 0:
|
|
defAction = mActionMoveFeature;
|
|
break;
|
|
case 1:
|
|
defAction = mActionMoveFeatureCopy;
|
|
break;
|
|
};
|
|
moveFeatureButton->setDefaultAction( defAction );
|
|
connect( moveFeatureButton, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
mDigitizeToolBar->insertWidget( mActionNodeTool, moveFeatureButton );
|
|
|
|
bt = new QToolButton();
|
|
bt->setPopupMode( QToolButton::MenuButtonPopup );
|
|
bt->addAction( mActionRotatePointSymbols );
|
|
bt->addAction( mActionOffsetPointSymbol );
|
|
|
|
QAction *defPointSymbolAction = mActionRotatePointSymbols;
|
|
switch ( settings.value( QStringLiteral( "UI/defaultPointSymbolAction" ), 0 ).toInt() )
|
|
{
|
|
case 0:
|
|
defPointSymbolAction = mActionRotatePointSymbols;
|
|
break;
|
|
case 1:
|
|
defPointSymbolAction = mActionOffsetPointSymbol;
|
|
break;
|
|
}
|
|
bt->setDefaultAction( defPointSymbolAction );
|
|
QAction *pointSymbolAction = mAdvancedDigitizeToolBar->addWidget( bt );
|
|
pointSymbolAction->setObjectName( QStringLiteral( "ActionPointSymbolTools" ) );
|
|
connect( bt, &QToolButton::triggered, this, &QgisApp::toolButtonActionTriggered );
|
|
|
|
// Cad toolbar
|
|
mAdvancedDigitizeToolBar->insertAction( mActionRotateFeature, mAdvancedDigitizingDockWidget->enableAction() );
|
|
}
|
|
|
|
void QgisApp::createStatusBar()
|
|
{
|
|
//remove borders from children under Windows
|
|
statusBar()->setStyleSheet( QStringLiteral( "QStatusBar::item {border: none;}" ) );
|
|
|
|
mStatusBar = new QgsStatusBar();
|
|
|
|
statusBar()->addPermanentWidget( mStatusBar, 10 );
|
|
|
|
// Add a panel to the status bar for the scale, coords and progress
|
|
// And also rendering suppression checkbox
|
|
mProgressBar = new QProgressBar( mStatusBar );
|
|
mProgressBar->setObjectName( QStringLiteral( "mProgressBar" ) );
|
|
mProgressBar->setMaximumWidth( 100 );
|
|
mProgressBar->hide();
|
|
mProgressBar->setWhatsThis( tr( "Progress bar that displays the status "
|
|
"of rendering layers and other time-intensive operations" ) );
|
|
mStatusBar->addPermanentWidget( mProgressBar, 1 );
|
|
|
|
connect( mMapCanvas, &QgsMapCanvas::renderStarting, this, &QgisApp::canvasRefreshStarted );
|
|
connect( mMapCanvas, &QgsMapCanvas::mapCanvasRefreshed, this, &QgisApp::canvasRefreshFinished );
|
|
|
|
mTaskManagerWidget = new QgsTaskManagerStatusBarWidget( QgsApplication::taskManager(), mStatusBar );
|
|
mStatusBar->addPermanentWidget( mTaskManagerWidget, 0 );
|
|
|
|
// Bumped the font up one point size since 8 was too
|
|
// small on some platforms. A point size of 9 still provides
|
|
// plenty of display space on 1024x768 resolutions
|
|
QFont myFont( QStringLiteral( "Arial" ), 9 );
|
|
statusBar()->setFont( myFont );
|
|
|
|
//coords status bar widget
|
|
mCoordsEdit = new QgsStatusBarCoordinatesWidget( mStatusBar );
|
|
mCoordsEdit->setObjectName( QStringLiteral( "mCoordsEdit" ) );
|
|
mCoordsEdit->setMapCanvas( mMapCanvas );
|
|
mCoordsEdit->setFont( myFont );
|
|
mStatusBar->addPermanentWidget( mCoordsEdit, 0 );
|
|
|
|
mScaleWidget = new QgsStatusBarScaleWidget( mMapCanvas, mStatusBar );
|
|
mScaleWidget->setObjectName( QStringLiteral( "mScaleWidget" ) );
|
|
mScaleWidget->setFont( myFont );
|
|
connect( mScaleWidget, &QgsStatusBarScaleWidget::scaleLockChanged, mMapCanvas, &QgsMapCanvas::setScaleLocked );
|
|
mStatusBar->addPermanentWidget( mScaleWidget, 0 );
|
|
|
|
// zoom widget
|
|
mMagnifierWidget = new QgsStatusBarMagnifierWidget( mStatusBar );
|
|
mMagnifierWidget->setObjectName( QStringLiteral( "mMagnifierWidget" ) );
|
|
mMagnifierWidget->setFont( myFont );
|
|
connect( mMapCanvas, &QgsMapCanvas::magnificationChanged, mMagnifierWidget, &QgsStatusBarMagnifierWidget::updateMagnification );
|
|
connect( mMagnifierWidget, &QgsStatusBarMagnifierWidget::magnificationChanged, mMapCanvas, &QgsMapCanvas::setMagnificationFactor );
|
|
mMagnifierWidget->updateMagnification( QSettings().value( QStringLiteral( "/qgis/magnifier_factor_default" ), 1.0 ).toDouble() );
|
|
mStatusBar->addPermanentWidget( mMagnifierWidget, 0 );
|
|
|
|
// add a widget to show/set current rotation
|
|
mRotationLabel = new QLabel( QString(), mStatusBar );
|
|
mRotationLabel->setObjectName( QStringLiteral( "mRotationLabel" ) );
|
|
mRotationLabel->setFont( myFont );
|
|
mRotationLabel->setMinimumWidth( 10 );
|
|
//mRotationLabel->setMaximumHeight( 20 );
|
|
mRotationLabel->setMargin( 3 );
|
|
mRotationLabel->setAlignment( Qt::AlignCenter );
|
|
mRotationLabel->setFrameStyle( QFrame::NoFrame );
|
|
mRotationLabel->setText( tr( "Rotation" ) );
|
|
mRotationLabel->setToolTip( tr( "Current clockwise map rotation in degrees" ) );
|
|
mStatusBar->addPermanentWidget( mRotationLabel, 0 );
|
|
|
|
mRotationEdit = new QgsDoubleSpinBox( mStatusBar );
|
|
mRotationEdit->setObjectName( QStringLiteral( "mRotationEdit" ) );
|
|
mRotationEdit->setClearValue( 0.0 );
|
|
mRotationEdit->setKeyboardTracking( false );
|
|
mRotationEdit->setMaximumWidth( 120 );
|
|
mRotationEdit->setDecimals( 1 );
|
|
mRotationEdit->setRange( -360.0, 360.0 );
|
|
mRotationEdit->setWrapping( true );
|
|
mRotationEdit->setSingleStep( 5.0 );
|
|
mRotationEdit->setFont( myFont );
|
|
mRotationEdit->setSuffix( trUtf8( " °" ) );
|
|
mRotationEdit->setWhatsThis( tr( "Shows the current map clockwise rotation "
|
|
"in degrees. It also allows editing to set "
|
|
"the rotation" ) );
|
|
mRotationEdit->setToolTip( tr( "Current clockwise map rotation in degrees" ) );
|
|
mStatusBar->addPermanentWidget( mRotationEdit, 0 );
|
|
connect( mRotationEdit, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgisApp::userRotation );
|
|
|
|
showRotation();
|
|
|
|
// render suppression status bar widget
|
|
mRenderSuppressionCBox = new QCheckBox( tr( "Render" ), mStatusBar );
|
|
mRenderSuppressionCBox->setObjectName( QStringLiteral( "mRenderSuppressionCBox" ) );
|
|
mRenderSuppressionCBox->setChecked( true );
|
|
mRenderSuppressionCBox->setFont( myFont );
|
|
mRenderSuppressionCBox->setWhatsThis( tr( "When checked, the map layers "
|
|
"are rendered in response to map navigation commands and other "
|
|
"events. When not checked, no rendering is done. This allows you "
|
|
"to add a large number of layers and symbolize them before rendering." ) );
|
|
mRenderSuppressionCBox->setToolTip( tr( "Toggle map rendering" ) );
|
|
mStatusBar->addPermanentWidget( mRenderSuppressionCBox, 0 );
|
|
// On the fly projection status bar icon
|
|
// Changed this to a tool button since a QPushButton is
|
|
// sculpted on OS X and the icon is never displayed [gsherman]
|
|
mOnTheFlyProjectionStatusButton = new QToolButton( mStatusBar );
|
|
mOnTheFlyProjectionStatusButton->setAutoRaise( true );
|
|
mOnTheFlyProjectionStatusButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
|
|
mOnTheFlyProjectionStatusButton->setObjectName( QStringLiteral( "mOntheFlyProjectionStatusButton" ) );
|
|
// Maintain uniform widget height in status bar by setting button height same as labels
|
|
// For Qt/Mac 3.3, the default toolbutton height is 30 and labels were expanding to match
|
|
mOnTheFlyProjectionStatusButton->setMaximumHeight( mScaleWidget->height() );
|
|
mOnTheFlyProjectionStatusButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconProjectionEnabled.svg" ) ) );
|
|
mOnTheFlyProjectionStatusButton->setWhatsThis( tr( "This icon shows whether "
|
|
"on the fly coordinate reference system transformation is enabled or not. "
|
|
"Click the icon to bring up "
|
|
"the project properties dialog to alter this behavior." ) );
|
|
mOnTheFlyProjectionStatusButton->setToolTip( tr( "CRS status - Click "
|
|
"to open coordinate reference system dialog" ) );
|
|
connect( mOnTheFlyProjectionStatusButton, &QAbstractButton::clicked,
|
|
this, &QgisApp::projectPropertiesProjections );//bring up the project props dialog when clicked
|
|
mStatusBar->addPermanentWidget( mOnTheFlyProjectionStatusButton, 0 );
|
|
mStatusBar->showMessage( tr( "Ready" ) );
|
|
|
|
mMessageButton = new QToolButton( mStatusBar );
|
|
mMessageButton->setAutoRaise( true );
|
|
mMessageButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mMessageLogRead.svg" ) ) );
|
|
mMessageButton->setToolTip( tr( "Messages" ) );
|
|
mMessageButton->setWhatsThis( tr( "Messages" ) );
|
|
mMessageButton->setObjectName( QStringLiteral( "mMessageLogViewerButton" ) );
|
|
mMessageButton->setMaximumHeight( mScaleWidget->height() );
|
|
mMessageButton->setCheckable( true );
|
|
mStatusBar->addPermanentWidget( mMessageButton, 0 );
|
|
|
|
mLocatorWidget = new QgsLocatorWidget( mStatusBar );
|
|
mStatusBar->addPermanentWidget( mLocatorWidget, 0, QgsStatusBar::AnchorLeft );
|
|
QShortcut *locatorShortCut = new QShortcut( QKeySequence( tr( "Ctrl+K" ) ), this );
|
|
connect( locatorShortCut, &QShortcut::activated, mLocatorWidget, [ = ] { mLocatorWidget->search( QString() ); } );
|
|
locatorShortCut->setObjectName( QStringLiteral( "Locator" ) );
|
|
locatorShortCut->setWhatsThis( tr( "Trigger Locator" ) );
|
|
|
|
mLocatorWidget->locator()->registerFilter( new QgsLayerTreeLocatorFilter() );
|
|
mLocatorWidget->locator()->registerFilter( new QgsLayoutLocatorFilter() );
|
|
QList< QWidget *> actionObjects;
|
|
actionObjects << menuBar()
|
|
<< mAdvancedDigitizeToolBar
|
|
<< mFileToolBar
|
|
<< mDataSourceManagerToolBar
|
|
<< mLayerToolBar
|
|
<< mDigitizeToolBar
|
|
<< mMapNavToolBar
|
|
<< mAttributesToolBar
|
|
<< mPluginToolBar
|
|
<< mRasterToolBar
|
|
<< mLabelToolBar
|
|
<< mVectorToolBar
|
|
<< mDatabaseToolBar
|
|
<< mWebToolBar
|
|
<< mSnappingToolBar;
|
|
|
|
mLocatorWidget->locator()->registerFilter( new QgsActionLocatorFilter( actionObjects ) );
|
|
mLocatorWidget->locator()->registerFilter( new QgsActiveLayerFeaturesLocatorFilter() );
|
|
}
|
|
|
|
void QgisApp::setIconSizes( int size )
|
|
{
|
|
int dockSize = dockedToolbarIconSize( size );
|
|
|
|
//Set the icon size of for all the toolbars created in the future.
|
|
setIconSize( QSize( size, size ) );
|
|
|
|
//Change all current icon sizes.
|
|
QList<QToolBar *> toolbars = findChildren<QToolBar *>();
|
|
Q_FOREACH ( QToolBar *toolbar, toolbars )
|
|
{
|
|
QString className = toolbar->parent()->metaObject()->className();
|
|
if ( className == QLatin1String( "QgisApp" ) )
|
|
{
|
|
toolbar->setIconSize( QSize( size, size ) );
|
|
}
|
|
else
|
|
{
|
|
toolbar->setIconSize( QSize( dockSize, dockSize ) );
|
|
}
|
|
}
|
|
|
|
Q_FOREACH ( QgsComposer *c, mPrintComposers )
|
|
{
|
|
c->setIconSizes( size );
|
|
}
|
|
Q_FOREACH ( QgsLayoutDesignerDialog *d, mLayoutDesignerDialogs )
|
|
{
|
|
d->setIconSizes( size );
|
|
}
|
|
}
|
|
|
|
void QgisApp::setTheme( const QString &themeName )
|
|
{
|
|
/*****************************************************************
|
|
// Init the toolbar icons by setting the icon for each action.
|
|
// All toolbar/menu items must be a QAction in order for this
|
|
// to work.
|
|
//
|
|
// When new toolbar/menu QAction objects are added to the interface,
|
|
// add an entry below to set the icon
|
|
//
|
|
// PNG names must match those defined for the default theme. The
|
|
// default theme is installed in <prefix>/share/qgis/themes/default.
|
|
//
|
|
// New core themes can be added by creating a subdirectory under src/themes
|
|
// and modifying the appropriate CMakeLists.txt files. User contributed themes
|
|
// will be installed directly into <prefix>/share/qgis/themes/<themedir>.
|
|
//
|
|
// Themes can be selected from the preferences dialog. The dialog parses
|
|
// the themes directory and builds a list of themes (ie subdirectories)
|
|
// for the user to choose from.
|
|
//
|
|
*/
|
|
QgsApplication::setUITheme( themeName );
|
|
//QgsDebugMsg("Setting theme to \n" + themeName);
|
|
mActionNewProject->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileNew.svg" ) ) );
|
|
mActionOpenProject->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileOpen.svg" ) ) );
|
|
mActionSaveProject->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileSave.svg" ) ) );
|
|
mActionSaveProjectAs->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileSaveAs.svg" ) ) );
|
|
mActionNewPrintComposer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewComposer.svg" ) ) );
|
|
mActionShowComposerManager->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionComposerManager.svg" ) ) );
|
|
mActionSaveMapAsImage->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveMapAsImage.svg" ) ) );
|
|
mActionSaveMapAsPdf->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAsPDF.svg" ) ) );
|
|
mActionExit->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFileExit.png" ) ) );
|
|
mActionAddOgrLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddOgrLayer.svg" ) ) );
|
|
mActionAddRasterLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddRasterLayer.svg" ) ) );
|
|
#ifdef HAVE_POSTGRESQL
|
|
mActionAddPgLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPostgisLayer.svg" ) ) );
|
|
#endif
|
|
mActionNewSpatiaLiteLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewSpatiaLiteLayer.svg" ) ) );
|
|
mActionAddSpatiaLiteLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddSpatiaLiteLayer.svg" ) ) );
|
|
mActionAddMssqlLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddMssqlLayer.svg" ) ) );
|
|
mActionAddDb2Layer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddDb2Layer.svg" ) ) );
|
|
#ifdef HAVE_ORACLE
|
|
mActionAddOracleLayer->setIcon( QgsApplication::getThemeIcon( "/mActionAddOracleLayer.svg" ) );
|
|
#endif
|
|
mActionRemoveLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveLayer.svg" ) ) );
|
|
mActionDuplicateLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateLayer.svg" ) ) );
|
|
mActionSetLayerCRS->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSetLayerCRS.png" ) ) );
|
|
mActionSetProjectCRSFromLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSetProjectCRSFromLayer.png" ) ) );
|
|
mActionNewVectorLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewVectorLayer.svg" ) ) );
|
|
mActionDataSourceManager->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDataSourceManager.svg" ) ) );
|
|
mActionNewMemoryLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCreateMemory.svg" ) ) );
|
|
mActionAddAllToOverview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddAllToOverview.svg" ) ) );
|
|
mActionHideAllLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHideAllLayers.svg" ) ) );
|
|
mActionShowAllLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowAllLayers.svg" ) ) );
|
|
mActionHideSelectedLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHideSelectedLayers.svg" ) ) );
|
|
mActionHideDeselectedLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHideDeselectedLayers.svg" ) ) );
|
|
mActionShowSelectedLayers->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowSelectedLayers.svg" ) ) );
|
|
mActionRemoveAllFromOverview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveAllFromOverview.svg" ) ) );
|
|
mActionToggleFullScreen->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleFullScreen.png" ) ) );
|
|
mActionProjectProperties->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionProjectProperties.png" ) ) );
|
|
mActionManagePlugins->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowPluginManager.svg" ) ) );
|
|
mActionInstallFromZip->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInstallPluginFromZip.svg" ) ) );
|
|
mActionShowPythonDialog->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconRunConsole.png" ) ) );
|
|
mActionCheckQgisVersion->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSuccess.svg" ) ) );
|
|
mActionOptions->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOptions.svg" ) ) );
|
|
mActionConfigureShortcuts->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionKeyboardShortcuts.svg" ) ) );
|
|
mActionCustomization->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInterfaceCustomization.svg" ) ) );
|
|
mActionHelpContents->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHelpContents.svg" ) ) );
|
|
mActionLocalHistogramStretch->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLocalHistogramStretch.svg" ) ) );
|
|
mActionFullHistogramStretch->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFullHistogramStretch.svg" ) ) );
|
|
mActionIncreaseBrightness->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIncreaseBrightness.svg" ) ) );
|
|
mActionDecreaseBrightness->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDecreaseBrightness.svg" ) ) );
|
|
mActionIncreaseContrast->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIncreaseContrast.svg" ) ) );
|
|
mActionDecreaseContrast->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDecreaseContrast.svg" ) ) );
|
|
mActionZoomActualSize->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomNative.png" ) ) );
|
|
mActionQgisHomePage->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionQgisHomePage.png" ) ) );
|
|
mActionAbout->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHelpAbout.svg" ) ) );
|
|
mActionSponsors->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHelpSponsors.png" ) ) );
|
|
mActionDraw->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDraw.svg" ) ) );
|
|
mActionToggleEditing->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleEditing.svg" ) ) );
|
|
mActionSaveLayerEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAllEdits.svg" ) ) );
|
|
mActionAllEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAllEdits.svg" ) ) );
|
|
mActionSaveEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveEdits.svg" ) ) );
|
|
mActionSaveAllEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSaveAllEdits.svg" ) ) );
|
|
mActionRollbackEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRollbackEdits.svg" ) ) );
|
|
mActionRollbackAllEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRollbackAllEdits.svg" ) ) );
|
|
mActionCancelEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCancelEdits.svg" ) ) );
|
|
mActionCancelAllEdits->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCancelAllEdits.svg" ) ) );
|
|
mActionCutFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCut.svg" ) ) );
|
|
mActionCopyFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ) );
|
|
mActionPasteFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditPaste.svg" ) ) );
|
|
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) ) );
|
|
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeaturePoint.svg" ) ) );
|
|
mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyPoint.svg" ) ) );
|
|
mActionRotateFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRotateFeature.svg" ) ) );
|
|
mActionReshapeFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionReshape.svg" ) ) );
|
|
mActionSplitFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSplitFeatures.svg" ) ) );
|
|
mActionSplitParts->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSplitParts.svg" ) ) );
|
|
mActionDeleteSelected->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ) );
|
|
mActionNodeTool->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNodeTool.svg" ) ) );
|
|
mActionSimplifyFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSimplify.svg" ) ) );
|
|
mActionUndo->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUndo.svg" ) ) );
|
|
mActionRedo->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRedo.svg" ) ) );
|
|
mActionAddRing->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddRing.svg" ) ) );
|
|
mActionFillRing->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFillRing.svg" ) ) );
|
|
mActionAddPart->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddPart.svg" ) ) );
|
|
mActionDeleteRing->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteRing.svg" ) ) );
|
|
mActionDeletePart->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeletePart.svg" ) ) );
|
|
mActionMergeFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMergeFeatures.svg" ) ) );
|
|
mActionOffsetCurve->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOffsetCurve.svg" ) ) );
|
|
mActionMergeFeatureAttributes->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMergeFeatureAttributes.svg" ) ) );
|
|
mActionRotatePointSymbols->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionRotatePointSymbols.svg" ) ) );
|
|
mActionOffsetPointSymbol->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionOffsetPointSymbols.svg" ) ) );
|
|
mActionZoomIn->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomIn.svg" ) ) );
|
|
mActionZoomOut->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomOut.svg" ) ) );
|
|
mActionZoomFullExtent->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomFullExtent.svg" ) ) );
|
|
mActionZoomToSelected->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToSelected.svg" ) ) );
|
|
mActionShowRasterCalculator->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowRasterCalculator.png" ) ) );
|
|
mActionPan->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPan.svg" ) ) );
|
|
mActionPanToSelected->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanToSelected.svg" ) ) );
|
|
mActionZoomLast->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomLast.svg" ) ) );
|
|
mActionZoomNext->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomNext.svg" ) ) );
|
|
mActionZoomToLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomToLayer.svg" ) ) );
|
|
mActionZoomActualSize->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomActual.svg" ) ) );
|
|
mActionIdentify->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionIdentify.svg" ) ) );
|
|
mActionFeatureAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mAction.svg" ) ) );
|
|
mActionSelectFeatures->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectRectangle.svg" ) ) );
|
|
mActionSelectPolygon->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectPolygon.svg" ) ) );
|
|
mActionSelectFreehand->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectFreehand.svg" ) ) );
|
|
mActionSelectRadius->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectRadius.svg" ) ) );
|
|
mActionDeselectAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeselectAll.svg" ) ) );
|
|
mActionSelectAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSelectAll.svg" ) ) );
|
|
mActionInvertSelection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInvertSelection.svg" ) ) );
|
|
mActionSelectByExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionSelect.svg" ) ) );
|
|
mActionSelectByForm->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
|
|
mActionOpenTable->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) ) );
|
|
mActionOpenFieldCalc->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCalculateField.svg" ) ) );
|
|
mActionMeasure->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMeasure.svg" ) ) );
|
|
mActionMeasureArea->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMeasureArea.svg" ) ) );
|
|
mActionMeasureAngle->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMeasureAngle.svg" ) ) );
|
|
mActionMapTips->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapTips.svg" ) ) );
|
|
mActionShowBookmarks->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowBookmarks.svg" ) ) );
|
|
mActionNewBookmark->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewBookmark.svg" ) ) );
|
|
mActionCustomProjection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCustomProjection.svg" ) ) );
|
|
mActionAddWmsLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddWmsLayer.svg" ) ) );
|
|
mActionAddWcsLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddWcsLayer.svg" ) ) );
|
|
mActionAddWfsLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddWfsLayer.svg" ) ) );
|
|
mActionAddAfsLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddAfsLayer.svg" ) ) );
|
|
mActionAddAmsLayer->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddAmsLayer.svg" ) ) );
|
|
mActionAddToOverview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInOverview.svg" ) ) );
|
|
mActionAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAnnotation.svg" ) ) );
|
|
mActionFormAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFormAnnotation.svg" ) ) );
|
|
mActionHtmlAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHtmlAnnotation.svg" ) ) );
|
|
mActionSvgAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSvgAnnotation.svg" ) ) );
|
|
mActionTextAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionTextAnnotation.svg" ) ) );
|
|
mActionLabeling->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLabeling.svg" ) ) );
|
|
mActionShowPinnedLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowPinnedLabels.svg" ) ) );
|
|
mActionPinLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPinLabels.svg" ) ) );
|
|
mActionShowHideLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowHideLabels.svg" ) ) );
|
|
mActionMoveLabel->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveLabel.svg" ) ) );
|
|
mActionRotateLabel->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRotateLabel.svg" ) ) );
|
|
mActionChangeLabelProperties->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionChangeLabelProperties.svg" ) ) );
|
|
mActionDiagramProperties->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/diagram.svg" ) ) );
|
|
mActionDecorationCopyright->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/copyright_label.svg" ) ) );
|
|
mActionDecorationNorthArrow->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/north_arrow.png" ) ) );
|
|
mActionDecorationScaleBar->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleBar.svg" ) ) );
|
|
mActionDecorationGrid->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/transformed.svg" ) ) );
|
|
|
|
//change themes of all composers
|
|
QSet<QgsComposer *>::const_iterator composerIt = mPrintComposers.constBegin();
|
|
for ( ; composerIt != mPrintComposers.constEnd(); ++composerIt )
|
|
{
|
|
( *composerIt )->setupTheme();
|
|
}
|
|
|
|
emit currentThemeChanged( themeName );
|
|
}
|
|
|
|
void QgisApp::setupConnections()
|
|
{
|
|
// connect the "cleanup" slot
|
|
connect( qApp, &QApplication::aboutToQuit, this, &QgisApp::saveWindowState );
|
|
|
|
// signal when mouse moved over window (coords display in status bar)
|
|
connect( mMapCanvas, &QgsMapCanvas::xyCoordinates, this, &QgisApp::saveLastMousePosition );
|
|
connect( mMapCanvas, &QgsMapCanvas::extentsChanged, this, &QgisApp::extentChanged );
|
|
connect( mMapCanvas, &QgsMapCanvas::scaleChanged, this, &QgisApp::showScale );
|
|
connect( mMapCanvas, &QgsMapCanvas::rotationChanged, this, &QgisApp::showRotation );
|
|
connect( mMapCanvas, &QgsMapCanvas::scaleChanged,
|
|
this, &QgisApp::updateMouseCoordinatePrecision );
|
|
connect( mMapCanvas, &QgsMapCanvas::mapToolSet,
|
|
this, &QgisApp::mapToolChanged );
|
|
connect( mMapCanvas, &QgsMapCanvas::selectionChanged,
|
|
this, &QgisApp::selectionChanged );
|
|
connect( mMapCanvas, &QgsMapCanvas::extentsChanged,
|
|
this, &QgisApp::markDirty );
|
|
connect( mMapCanvas, &QgsMapCanvas::layersChanged,
|
|
this, &QgisApp::markDirty );
|
|
|
|
connect( mMapCanvas, &QgsMapCanvas::zoomLastStatusChanged,
|
|
mActionZoomLast, &QAction::setEnabled );
|
|
connect( mMapCanvas, &QgsMapCanvas::zoomNextStatusChanged,
|
|
mActionZoomNext, &QAction::setEnabled );
|
|
connect( mRenderSuppressionCBox, &QAbstractButton::toggled,
|
|
this, [ = ]( bool flag )
|
|
{
|
|
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
|
|
canvas->setRenderFlag( flag );
|
|
}
|
|
);
|
|
|
|
connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged,
|
|
this, &QgisApp::reprojectAnnotations );
|
|
|
|
// connect MapCanvas keyPress event so we can check if selected feature collection must be deleted
|
|
connect( mMapCanvas, &QgsMapCanvas::keyPressed,
|
|
this, &QgisApp::mapCanvas_keyPressed );
|
|
|
|
// project crs connections
|
|
connect( QgsProject::instance(), &QgsProject::crsChanged,
|
|
this, &QgisApp::updateCrsStatusBar );
|
|
connect( QgsProject::instance(), &QgsProject::crsChanged,
|
|
this, [ = ]
|
|
{
|
|
QgsDebugMsgLevel( QString( "QgisApp::setupConnections -1- : QgsProject::instance()->crs().description[%1]ellipsoid[%2]" ).arg( QgsProject::instance()->crs().description() ).arg( QgsProject::instance()->crs().ellipsoidAcronym() ), 3 );
|
|
mMapCanvas->setDestinationCrs( QgsProject::instance()->crs() );
|
|
} );
|
|
|
|
connect( QgsProject::instance(), &QgsProject::labelingEngineSettingsChanged,
|
|
this, [ = ]
|
|
{
|
|
mMapCanvas->setLabelingEngineSettings( QgsProject::instance()->labelingEngineSettings() );
|
|
} );
|
|
|
|
// connect legend signals
|
|
connect( mLayerTreeView, &QgsLayerTreeView::currentLayerChanged,
|
|
this, &QgisApp::activateDeactivateLayerRelatedActions );
|
|
connect( mLayerTreeView, &QgsLayerTreeView::currentLayerChanged,
|
|
this, &QgisApp::setMapStyleDockLayer );
|
|
|
|
connect( mLayerTreeView->selectionModel(), &QItemSelectionModel::selectionChanged,
|
|
this, &QgisApp::legendLayerSelectionChanged );
|
|
connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::addedChildren,
|
|
this, &QgisApp::markDirty );
|
|
connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::addedChildren,
|
|
this, &QgisApp::updateNewLayerInsertionPoint );
|
|
connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::removedChildren,
|
|
this, &QgisApp::markDirty );
|
|
connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::removedChildren,
|
|
this, &QgisApp::updateNewLayerInsertionPoint );
|
|
connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::visibilityChanged,
|
|
this, &QgisApp::markDirty );
|
|
connect( mLayerTreeView->layerTreeModel()->rootGroup(), &QgsLayerTreeNode::customPropertyChanged,
|
|
this, &QgisApp::markDirty );
|
|
|
|
// connect map layer registry
|
|
connect( QgsProject::instance(), &QgsProject::layersAdded,
|
|
this, &QgisApp::layersWereAdded );
|
|
connect( QgsProject::instance(),
|
|
static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ),
|
|
this, &QgisApp::removingLayers );
|
|
|
|
// connect initialization signal
|
|
connect( this, &QgisApp::initializationCompleted,
|
|
this, &QgisApp::fileOpenAfterLaunch );
|
|
|
|
// Connect warning dialog from project reading
|
|
connect( QgsProject::instance(), &QgsProject::oldProjectVersionWarning,
|
|
this, &QgisApp::oldProjectVersionWarning );
|
|
connect( QgsProject::instance(), &QgsProject::layerLoaded,
|
|
this, &QgisApp::showProgress );
|
|
connect( QgsProject::instance(), &QgsProject::loadingLayer,
|
|
this, &QgisApp::showStatusMessage );
|
|
connect( QgsProject::instance(), &QgsProject::readProject,
|
|
this, &QgisApp::readProject );
|
|
connect( QgsProject::instance(), &QgsProject::writeProject,
|
|
this, &QgisApp::writeProject );
|
|
|
|
connect( this, &QgisApp::projectRead,
|
|
this, &QgisApp::fileOpenedOKAfterLaunch );
|
|
|
|
connect( QgsProject::instance(), &QgsProject::transactionGroupsChanged, this, &QgisApp::onTransactionGroupsChanged );
|
|
|
|
// connect preview modes actions
|
|
connect( mActionPreviewModeOff, &QAction::triggered, this, &QgisApp::disablePreviewMode );
|
|
connect( mActionPreviewModeGrayscale, &QAction::triggered, this, &QgisApp::activateGrayscalePreview );
|
|
connect( mActionPreviewModeMono, &QAction::triggered, this, &QgisApp::activateMonoPreview );
|
|
connect( mActionPreviewProtanope, &QAction::triggered, this, &QgisApp::activateProtanopePreview );
|
|
connect( mActionPreviewDeuteranope, &QAction::triggered, this, &QgisApp::activateDeuteranopePreview );
|
|
|
|
// setup undo/redo actions
|
|
connect( mUndoWidget, &QgsUndoWidget::undoStackChanged, this, &QgisApp::updateUndoActions );
|
|
|
|
connect( mPrintComposersMenu, &QMenu::aboutToShow, this, &QgisApp::composerMenuAboutToShow );
|
|
connect( QgsProject::instance()->layoutManager(), &QgsLayoutManager::compositionAboutToBeRemoved, this, &QgisApp::compositionAboutToBeRemoved );
|
|
}
|
|
|
|
void QgisApp::createCanvasTools()
|
|
{
|
|
// create tools
|
|
mMapTools.mZoomIn = new QgsMapToolZoom( mMapCanvas, false /* zoomIn */ );
|
|
mMapTools.mZoomIn->setAction( mActionZoomIn );
|
|
mMapTools.mZoomOut = new QgsMapToolZoom( mMapCanvas, true /* zoomOut */ );
|
|
mMapTools.mZoomOut->setAction( mActionZoomOut );
|
|
mMapTools.mPan = new QgsMapToolPan( mMapCanvas );
|
|
mMapTools.mPan->setAction( mActionPan );
|
|
mMapTools.mIdentify = new QgsMapToolIdentifyAction( mMapCanvas );
|
|
mMapTools.mIdentify->setAction( mActionIdentify );
|
|
connect( mMapTools.mIdentify, &QgsMapToolIdentifyAction::copyToClipboard,
|
|
this, &QgisApp::copyFeatures );
|
|
mMapTools.mFeatureAction = new QgsMapToolFeatureAction( mMapCanvas );
|
|
mMapTools.mFeatureAction->setAction( mActionFeatureAction );
|
|
mMapTools.mMeasureDist = new QgsMeasureTool( mMapCanvas, false /* area */ );
|
|
mMapTools.mMeasureDist->setAction( mActionMeasure );
|
|
mMapTools.mMeasureArea = new QgsMeasureTool( mMapCanvas, true /* area */ );
|
|
mMapTools.mMeasureArea->setAction( mActionMeasureArea );
|
|
mMapTools.mMeasureAngle = new QgsMapToolMeasureAngle( mMapCanvas );
|
|
mMapTools.mMeasureAngle->setAction( mActionMeasureAngle );
|
|
mMapTools.mTextAnnotation = new QgsMapToolTextAnnotation( mMapCanvas );
|
|
mMapTools.mTextAnnotation->setAction( mActionTextAnnotation );
|
|
mMapTools.mFormAnnotation = new QgsMapToolFormAnnotation( mMapCanvas );
|
|
mMapTools.mFormAnnotation->setAction( mActionFormAnnotation );
|
|
mMapTools.mHtmlAnnotation = new QgsMapToolHtmlAnnotation( mMapCanvas );
|
|
mMapTools.mHtmlAnnotation->setAction( mActionHtmlAnnotation );
|
|
mMapTools.mSvgAnnotation = new QgsMapToolSvgAnnotation( mMapCanvas );
|
|
mMapTools.mSvgAnnotation->setAction( mActionSvgAnnotation );
|
|
mMapTools.mAnnotation = new QgsMapToolAnnotation( mMapCanvas );
|
|
mMapTools.mAnnotation->setAction( mActionAnnotation );
|
|
mMapTools.mAddFeature = new QgsMapToolAddFeature( mMapCanvas, QgsMapToolCapture::CaptureNone );
|
|
mMapTools.mAddFeature->setAction( mActionAddFeature );
|
|
mMapTools.mCircularStringCurvePoint = new QgsMapToolCircularStringCurvePoint( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mCircularStringCurvePoint->setAction( mActionCircularStringCurvePoint );
|
|
mMapTools.mCircularStringRadius = new QgsMapToolCircularStringRadius( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mCircularStringRadius->setAction( mActionCircularStringRadius );
|
|
mMapTools.mCircle2Points = new QgsMapToolCircle2Points( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mCircle2Points->setAction( mActionCircle2Points );
|
|
mMapTools.mCircle3Points = new QgsMapToolCircle3Points( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mCircle3Points->setAction( mActionCircle3Points );
|
|
mMapTools.mCircle3Tangents = new QgsMapToolCircle3Tangents( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mCircle3Tangents->setAction( mActionCircle3Tangents );
|
|
mMapTools.mCircle2TangentsPoint = new QgsMapToolCircle2TangentsPoint( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mCircle2TangentsPoint->setAction( mActionCircle2TangentsPoint );
|
|
mMapTools.mCircleCenterPoint = new QgsMapToolCircleCenterPoint( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mCircleCenterPoint->setAction( mActionCircleCenterPoint );
|
|
mMapTools.mEllipseCenter2Points = new QgsMapToolEllipseCenter2Points( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mEllipseCenter2Points->setAction( mActionEllipseCenter2Points );
|
|
mMapTools.mEllipseCenterPoint = new QgsMapToolEllipseCenterPoint( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mEllipseCenterPoint->setAction( mActionEllipseCenterPoint );
|
|
mMapTools.mEllipseExtent = new QgsMapToolEllipseExtent( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mEllipseExtent->setAction( mActionEllipseExtent );
|
|
mMapTools.mEllipseFoci = new QgsMapToolEllipseFoci( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mEllipseFoci->setAction( mActionEllipseFoci );
|
|
mMapTools.mRectangleCenterPoint = new QgsMapToolRectangleCenter( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mRectangleCenterPoint->setAction( mActionRectangleCenterPoint );
|
|
mMapTools.mRectangleExtent = new QgsMapToolRectangleExtent( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mRectangleExtent->setAction( mActionRectangleExtent );
|
|
mMapTools.mRectangle3Points = new QgsMapToolRectangle3Points( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mRectangle3Points->setAction( mActionRectangle3Points );
|
|
mMapTools.mRegularPolygon2Points = new QgsMapToolRegularPolygon2Points( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mRegularPolygon2Points->setAction( mActionRegularPolygon2Points );
|
|
mMapTools.mRegularPolygonCenterPoint = new QgsMapToolRegularPolygonCenterPoint( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mRegularPolygonCenterPoint->setAction( mActionRegularPolygonCenterPoint );
|
|
mMapTools.mRegularPolygonCenterCorner = new QgsMapToolRegularPolygonCenterCorner( mMapTools.mAddFeature, mMapCanvas );
|
|
mMapTools.mRegularPolygonCenterCorner->setAction( mActionRegularPolygonCenterCorner );
|
|
mMapTools.mMoveFeature = new QgsMapToolMoveFeature( mMapCanvas, QgsMapToolMoveFeature::Move );
|
|
mMapTools.mMoveFeature->setAction( mActionMoveFeature );
|
|
mMapTools.mMoveFeatureCopy = new QgsMapToolMoveFeature( mMapCanvas, QgsMapToolMoveFeature::CopyMove );
|
|
mMapTools.mMoveFeatureCopy->setAction( mActionMoveFeatureCopy );
|
|
mMapTools.mRotateFeature = new QgsMapToolRotateFeature( mMapCanvas );
|
|
mMapTools.mRotateFeature->setAction( mActionRotateFeature );
|
|
mMapTools.mOffsetCurve = new QgsMapToolOffsetCurve( mMapCanvas );
|
|
mMapTools.mOffsetCurve->setAction( mActionOffsetCurve );
|
|
mMapTools.mReshapeFeatures = new QgsMapToolReshape( mMapCanvas );
|
|
mMapTools.mReshapeFeatures->setAction( mActionReshapeFeatures );
|
|
mMapTools.mSplitFeatures = new QgsMapToolSplitFeatures( mMapCanvas );
|
|
mMapTools.mSplitFeatures->setAction( mActionSplitFeatures );
|
|
mMapTools.mSplitParts = new QgsMapToolSplitParts( mMapCanvas );
|
|
mMapTools.mSplitParts->setAction( mActionSplitParts );
|
|
mMapTools.mSelectFeatures = new QgsMapToolSelectFeatures( mMapCanvas );
|
|
mMapTools.mSelectFeatures->setAction( mActionSelectFeatures );
|
|
mMapTools.mSelectPolygon = new QgsMapToolSelectPolygon( mMapCanvas );
|
|
mMapTools.mSelectPolygon->setAction( mActionSelectPolygon );
|
|
mMapTools.mSelectFreehand = new QgsMapToolSelectFreehand( mMapCanvas );
|
|
mMapTools.mSelectFreehand->setAction( mActionSelectFreehand );
|
|
mMapTools.mSelectRadius = new QgsMapToolSelectRadius( mMapCanvas );
|
|
mMapTools.mSelectRadius->setAction( mActionSelectRadius );
|
|
mMapTools.mAddRing = new QgsMapToolAddRing( mMapCanvas );
|
|
mMapTools.mAddRing->setAction( mActionAddRing );
|
|
mMapTools.mFillRing = new QgsMapToolFillRing( mMapCanvas );
|
|
mMapTools.mFillRing->setAction( mActionFillRing );
|
|
mMapTools.mAddPart = new QgsMapToolAddPart( mMapCanvas );
|
|
mMapTools.mAddPart->setAction( mActionAddPart );
|
|
mMapTools.mSimplifyFeature = new QgsMapToolSimplify( mMapCanvas );
|
|
mMapTools.mSimplifyFeature->setAction( mActionSimplifyFeature );
|
|
mMapTools.mDeleteRing = new QgsMapToolDeleteRing( mMapCanvas );
|
|
mMapTools.mDeleteRing->setAction( mActionDeleteRing );
|
|
mMapTools.mDeletePart = new QgsMapToolDeletePart( mMapCanvas );
|
|
mMapTools.mDeletePart->setAction( mActionDeletePart );
|
|
mMapTools.mNodeTool = new QgsNodeTool( mMapCanvas, mAdvancedDigitizingDockWidget );
|
|
mMapTools.mNodeTool->setAction( mActionNodeTool );
|
|
mMapTools.mRotatePointSymbolsTool = new QgsMapToolRotatePointSymbols( mMapCanvas );
|
|
mMapTools.mRotatePointSymbolsTool->setAction( mActionRotatePointSymbols );
|
|
mMapTools.mOffsetPointSymbolTool = new QgsMapToolOffsetPointSymbol( mMapCanvas );
|
|
mMapTools.mOffsetPointSymbolTool->setAction( mActionOffsetPointSymbol );
|
|
|
|
mMapTools.mPinLabels = new QgsMapToolPinLabels( mMapCanvas );
|
|
mMapTools.mPinLabels->setAction( mActionPinLabels );
|
|
mMapTools.mShowHideLabels = new QgsMapToolShowHideLabels( mMapCanvas );
|
|
mMapTools.mShowHideLabels->setAction( mActionShowHideLabels );
|
|
mMapTools.mMoveLabel = new QgsMapToolMoveLabel( mMapCanvas );
|
|
mMapTools.mMoveLabel->setAction( mActionMoveLabel );
|
|
|
|
mMapTools.mRotateLabel = new QgsMapToolRotateLabel( mMapCanvas );
|
|
mMapTools.mRotateLabel->setAction( mActionRotateLabel );
|
|
mMapTools.mChangeLabelProperties = new QgsMapToolChangeLabelProperties( mMapCanvas );
|
|
mMapTools.mChangeLabelProperties->setAction( mActionChangeLabelProperties );
|
|
//ensure that non edit tool is initialized or we will get crashes in some situations
|
|
mNonEditMapTool = mMapTools.mPan;
|
|
}
|
|
|
|
void QgisApp::createOverview()
|
|
{
|
|
// overview canvas
|
|
mOverviewCanvas = new QgsMapOverviewCanvas( nullptr, mMapCanvas );
|
|
|
|
//set canvas color to default
|
|
QgsSettings settings;
|
|
int red = settings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
|
|
int green = settings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
|
|
int blue = settings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
|
|
mOverviewCanvas->setBackgroundColor( QColor( red, green, blue ) );
|
|
|
|
mOverviewCanvas->setWhatsThis( tr( "Map overview canvas. This canvas can be used to display a locator map that shows the current extent of the map canvas. The current extent is shown as a red rectangle. Any layer on the map can be added to the overview canvas." ) );
|
|
|
|
mOverviewMapCursor = new QCursor( Qt::OpenHandCursor );
|
|
mOverviewCanvas->setCursor( *mOverviewMapCursor );
|
|
// QVBoxLayout *myOverviewLayout = new QVBoxLayout;
|
|
// myOverviewLayout->addWidget(overviewCanvas);
|
|
// overviewFrame->setLayout(myOverviewLayout);
|
|
mOverviewDock = new QgsDockWidget( tr( "Overview Panel" ), this );
|
|
mOverviewDock->setObjectName( QStringLiteral( "Overview" ) );
|
|
mOverviewDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
|
|
mOverviewDock->setWidget( mOverviewCanvas );
|
|
addDockWidget( Qt::LeftDockWidgetArea, mOverviewDock );
|
|
// add to the Panel submenu
|
|
mPanelMenu->addAction( mOverviewDock->toggleViewAction() );
|
|
|
|
mLayerTreeCanvasBridge->setOvervewCanvas( mOverviewCanvas );
|
|
}
|
|
|
|
void QgisApp::addDockWidget( Qt::DockWidgetArea area, QDockWidget *thepDockWidget )
|
|
{
|
|
QMainWindow::addDockWidget( area, thepDockWidget );
|
|
// Make the right and left docks consume all vertical space and top
|
|
// and bottom docks nest between them
|
|
setCorner( Qt::TopLeftCorner, Qt::LeftDockWidgetArea );
|
|
setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea );
|
|
setCorner( Qt::TopRightCorner, Qt::RightDockWidgetArea );
|
|
setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea );
|
|
// add to the Panel submenu
|
|
mPanelMenu->addAction( thepDockWidget->toggleViewAction() );
|
|
|
|
thepDockWidget->show();
|
|
|
|
// refresh the map canvas
|
|
refreshMapCanvas();
|
|
}
|
|
|
|
void QgisApp::removeDockWidget( QDockWidget *thepDockWidget )
|
|
{
|
|
QMainWindow::removeDockWidget( thepDockWidget );
|
|
mPanelMenu->removeAction( thepDockWidget->toggleViewAction() );
|
|
}
|
|
|
|
QToolBar *QgisApp::addToolBar( const QString &name )
|
|
{
|
|
QToolBar *toolBar = QMainWindow::addToolBar( name );
|
|
// add to the Toolbar submenu
|
|
mToolbarMenu->addAction( toolBar->toggleViewAction() );
|
|
return toolBar;
|
|
}
|
|
|
|
void QgisApp::addToolBar( QToolBar *toolBar, Qt::ToolBarArea area )
|
|
{
|
|
QMainWindow::addToolBar( area, toolBar );
|
|
// add to the Toolbar submenu
|
|
mToolbarMenu->addAction( toolBar->toggleViewAction() );
|
|
}
|
|
|
|
QgsLayerTreeView *QgisApp::layerTreeView()
|
|
{
|
|
Q_ASSERT( mLayerTreeView );
|
|
return mLayerTreeView;
|
|
}
|
|
|
|
QgsPluginManager *QgisApp::pluginManager()
|
|
{
|
|
Q_ASSERT( mPluginManager );
|
|
return mPluginManager;
|
|
}
|
|
|
|
QgsUserProfileManager *QgisApp::userProfileManager()
|
|
{
|
|
Q_ASSERT( mUserProfileManager );
|
|
return mUserProfileManager;
|
|
}
|
|
|
|
QgsMapCanvas *QgisApp::mapCanvas()
|
|
{
|
|
Q_ASSERT( mMapCanvas );
|
|
return mMapCanvas;
|
|
}
|
|
|
|
QgsMapCanvas *QgisApp::createNewMapCanvas( const QString &name )
|
|
{
|
|
QgsMapCanvasDockWidget *dock = createNewMapCanvasDock( name );
|
|
if ( !dock )
|
|
return nullptr;
|
|
|
|
dock->mapCanvas()->setLayers( mMapCanvas->layers() );
|
|
dock->mapCanvas()->setExtent( mMapCanvas->extent() );
|
|
QgsDebugMsgLevel( QString( "QgisApp::createNewMapCanvas -2- : QgsProject::instance()->crs().description[%1]ellipsoid[%2]" ).arg( QgsProject::instance()->crs().description() ).arg( QgsProject::instance()->crs().ellipsoidAcronym() ), 3 );
|
|
dock->mapCanvas()->setDestinationCrs( QgsProject::instance()->crs() );
|
|
dock->mapCanvas()->freeze( false );
|
|
return dock->mapCanvas();
|
|
}
|
|
|
|
QgsMapCanvasDockWidget *QgisApp::createNewMapCanvasDock( const QString &name, bool isFloating, const QRect &dockGeometry, Qt::DockWidgetArea area )
|
|
{
|
|
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
|
|
{
|
|
if ( canvas->objectName() == name )
|
|
{
|
|
QgsDebugMsg( tr( "A map canvas with name '%1' already exists!" ).arg( name ) );
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
QgsMapCanvasDockWidget *mapCanvasWidget = new QgsMapCanvasDockWidget( name, this );
|
|
mapCanvasWidget->setAllowedAreas( Qt::AllDockWidgetAreas );
|
|
mapCanvasWidget->setMainCanvas( mMapCanvas );
|
|
|
|
QgsMapCanvas *mapCanvas = mapCanvasWidget->mapCanvas();
|
|
mapCanvas->freeze( true );
|
|
mapCanvas->setObjectName( name );
|
|
connect( mapCanvas, &QgsMapCanvas::messageEmitted, this, &QgisApp::displayMessage );
|
|
connect( mLayerTreeCanvasBridge, &QgsLayerTreeMapCanvasBridge::canvasLayersChanged, mapCanvas, &QgsMapCanvas::setLayers );
|
|
|
|
applyProjectSettingsToCanvas( mapCanvas );
|
|
applyDefaultSettingsToCanvas( mapCanvas );
|
|
|
|
// add existing annotations to canvas
|
|
Q_FOREACH ( QgsAnnotation *annotation, QgsProject::instance()->annotationManager()->annotations() )
|
|
{
|
|
QgsMapCanvasAnnotationItem *canvasItem = new QgsMapCanvasAnnotationItem( annotation, mapCanvas );
|
|
Q_UNUSED( canvasItem ); //item is already added automatically to canvas scene
|
|
}
|
|
|
|
markDirty();
|
|
connect( mapCanvasWidget, &QgsMapCanvasDockWidget::closed, this, &QgisApp::markDirty );
|
|
connect( mapCanvasWidget, &QgsMapCanvasDockWidget::renameTriggered, this, &QgisApp::renameView );
|
|
|
|
mapCanvasWidget->setFloating( isFloating );
|
|
if ( dockGeometry.isEmpty() )
|
|
{
|
|
// try to guess a nice initial placement for view - about 3/4 along, half way down
|
|
mapCanvasWidget->setGeometry( QRect( rect().width() * 0.75, rect().height() * 0.5, 400, 400 ) );
|
|
addDockWidget( area, mapCanvasWidget );
|
|
}
|
|
else
|
|
{
|
|
if ( !isFloating )
|
|
{
|
|
// ugly hack, but only way to set dock size correctly for Qt < 5.6
|
|
mapCanvasWidget->setFixedSize( dockGeometry.size() );
|
|
addDockWidget( area, mapCanvasWidget );
|
|
mapCanvasWidget->resize( dockGeometry.size() );
|
|
QgsApplication::processEvents(); // required!
|
|
mapCanvasWidget->setFixedSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX );
|
|
}
|
|
else
|
|
{
|
|
mapCanvasWidget->setGeometry( dockGeometry );
|
|
addDockWidget( area, mapCanvasWidget );
|
|
}
|
|
}
|
|
return mapCanvasWidget;
|
|
}
|
|
|
|
void QgisApp::closeMapCanvas( const QString &name )
|
|
{
|
|
Q_FOREACH ( QgsMapCanvasDockWidget *w, findChildren< QgsMapCanvasDockWidget * >() )
|
|
{
|
|
if ( w->mapCanvas()->objectName() == name )
|
|
{
|
|
w->close();
|
|
delete w;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::closeAdditionalMapCanvases()
|
|
{
|
|
freezeCanvases( true ); // closing docks may cause canvases to resize, and we don't want a map refresh occurring
|
|
Q_FOREACH ( QgsMapCanvasDockWidget *w, findChildren< QgsMapCanvasDockWidget * >() )
|
|
{
|
|
w->close();
|
|
delete w;
|
|
}
|
|
freezeCanvases( false );
|
|
}
|
|
|
|
void QgisApp::freezeCanvases( bool frozen )
|
|
{
|
|
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
|
|
{
|
|
canvas->freeze( frozen );
|
|
}
|
|
}
|
|
|
|
QgsMessageBar *QgisApp::messageBar()
|
|
{
|
|
Q_ASSERT( mInfoBar );
|
|
return mInfoBar;
|
|
}
|
|
|
|
void QgisApp::toggleLogMessageIcon( bool hasLogMessage )
|
|
{
|
|
if ( hasLogMessage && !mLogDock->isVisible() )
|
|
{
|
|
mMessageButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mMessageLog.svg" ) ) );
|
|
}
|
|
else
|
|
{
|
|
mMessageButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mMessageLogRead.svg" ) ) );
|
|
}
|
|
}
|
|
|
|
void QgisApp::openMessageLog()
|
|
{
|
|
mMessageButton->setChecked( true );
|
|
}
|
|
|
|
void QgisApp::addUserInputWidget( QWidget *widget )
|
|
{
|
|
mUserInputDockWidget->addUserInputWidget( widget );
|
|
}
|
|
|
|
|
|
void QgisApp::initLayerTreeView()
|
|
{
|
|
mLayerTreeView->setWhatsThis( tr( "Map legend that displays all the layers currently on the map canvas. Click on the checkbox to turn a layer on or off. Double-click on a layer in the legend to customize its appearance and set other properties." ) );
|
|
|
|
mLayerTreeDock = new QgsDockWidget( tr( "Layers Panel" ), this );
|
|
mLayerTreeDock->setObjectName( QStringLiteral( "Layers" ) );
|
|
mLayerTreeDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
|
|
|
|
QgsLayerTreeModel *model = new QgsLayerTreeModel( QgsProject::instance()->layerTreeRoot(), this );
|
|
#ifdef ENABLE_MODELTEST
|
|
new ModelTest( model, this );
|
|
#endif
|
|
model->setFlag( QgsLayerTreeModel::AllowNodeReorder );
|
|
model->setFlag( QgsLayerTreeModel::AllowNodeRename );
|
|
model->setFlag( QgsLayerTreeModel::AllowNodeChangeVisibility );
|
|
model->setFlag( QgsLayerTreeModel::ShowLegendAsTree );
|
|
model->setFlag( QgsLayerTreeModel::UseEmbeddedWidgets );
|
|
model->setAutoCollapseLegendNodes( 10 );
|
|
|
|
mLayerTreeView->setModel( model );
|
|
mLayerTreeView->setMenuProvider( new QgsAppLayerTreeViewMenuProvider( mLayerTreeView, mMapCanvas ) );
|
|
|
|
setupLayerTreeViewFromSettings();
|
|
|
|
connect( mLayerTreeView, &QAbstractItemView::doubleClicked, this, &QgisApp::layerTreeViewDoubleClicked );
|
|
connect( mLayerTreeView, &QgsLayerTreeView::currentLayerChanged, this, &QgisApp::activeLayerChanged );
|
|
connect( mLayerTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgisApp::updateNewLayerInsertionPoint );
|
|
connect( QgsProject::instance()->layerTreeRegistryBridge(), &QgsLayerTreeRegistryBridge::addedLayersToLayerTree,
|
|
this, &QgisApp::autoSelectAddedLayer );
|
|
|
|
// add group action
|
|
QAction *actionAddGroup = new QAction( tr( "Add Group" ), this );
|
|
actionAddGroup->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddGroup.svg" ) ) );
|
|
actionAddGroup->setToolTip( tr( "Add Group" ) );
|
|
connect( actionAddGroup, &QAction::triggered, mLayerTreeView->defaultActions(), &QgsLayerTreeViewDefaultActions::addGroup );
|
|
|
|
// visibility groups tool button
|
|
QToolButton *btnVisibilityPresets = new QToolButton;
|
|
btnVisibilityPresets->setAutoRaise( true );
|
|
btnVisibilityPresets->setToolTip( tr( "Manage Map Themes" ) );
|
|
btnVisibilityPresets->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowAllLayers.svg" ) ) );
|
|
btnVisibilityPresets->setPopupMode( QToolButton::InstantPopup );
|
|
btnVisibilityPresets->setMenu( QgsMapThemes::instance()->menu() );
|
|
|
|
// filter legend action
|
|
mActionFilterLegend = new QAction( tr( "Filter Legend By Map Content" ), this );
|
|
mActionFilterLegend->setCheckable( true );
|
|
mActionFilterLegend->setToolTip( tr( "Filter Legend By Map Content" ) );
|
|
mActionFilterLegend->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFilter2.svg" ) ) );
|
|
connect( mActionFilterLegend, &QAction::toggled, this, &QgisApp::updateFilterLegend );
|
|
|
|
mLegendExpressionFilterButton = new QgsLegendFilterButton( this );
|
|
mLegendExpressionFilterButton->setToolTip( tr( "Filter legend by expression" ) );
|
|
connect( mLegendExpressionFilterButton, &QAbstractButton::toggled, this, &QgisApp::toggleFilterLegendByExpression );
|
|
|
|
mActionStyleDock = new QAction( tr( "Layer Styling" ), this );
|
|
mActionStyleDock->setCheckable( true );
|
|
mActionStyleDock->setToolTip( tr( "Open the layer styling dock" ) );
|
|
mActionStyleDock->setShortcut( QStringLiteral( "F7" ) );
|
|
mActionStyleDock->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/symbology.svg" ) ) );
|
|
connect( mActionStyleDock, &QAction::toggled, this, &QgisApp::mapStyleDock );
|
|
|
|
// expand / collapse tool buttons
|
|
QAction *actionExpandAll = new QAction( tr( "Expand All" ), this );
|
|
actionExpandAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionExpandTree.svg" ) ) );
|
|
actionExpandAll->setToolTip( tr( "Expand All" ) );
|
|
connect( actionExpandAll, &QAction::triggered, mLayerTreeView, &QgsLayerTreeView::expandAllNodes );
|
|
QAction *actionCollapseAll = new QAction( tr( "Collapse All" ), this );
|
|
actionCollapseAll->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCollapseTree.svg" ) ) );
|
|
actionCollapseAll->setToolTip( tr( "Collapse All" ) );
|
|
connect( actionCollapseAll, &QAction::triggered, mLayerTreeView, &QgsLayerTreeView::collapseAllNodes );
|
|
|
|
QToolBar *toolbar = new QToolBar();
|
|
toolbar->setIconSize( QSize( 16, 16 ) );
|
|
toolbar->addAction( mActionStyleDock );
|
|
toolbar->addAction( actionAddGroup );
|
|
toolbar->addWidget( btnVisibilityPresets );
|
|
toolbar->addAction( mActionFilterLegend );
|
|
toolbar->addWidget( mLegendExpressionFilterButton );
|
|
toolbar->addAction( actionExpandAll );
|
|
toolbar->addAction( actionCollapseAll );
|
|
toolbar->addAction( mActionRemoveLayer );
|
|
|
|
QVBoxLayout *vboxLayout = new QVBoxLayout;
|
|
vboxLayout->setMargin( 0 );
|
|
vboxLayout->setContentsMargins( 0, 0, 0, 0 );
|
|
vboxLayout->setSpacing( 0 );
|
|
vboxLayout->addWidget( toolbar );
|
|
vboxLayout->addWidget( mLayerTreeView );
|
|
|
|
QWidget *w = new QWidget;
|
|
w->setLayout( vboxLayout );
|
|
mLayerTreeDock->setWidget( w );
|
|
addDockWidget( Qt::LeftDockWidgetArea, mLayerTreeDock );
|
|
|
|
mLayerTreeCanvasBridge = new QgsLayerTreeMapCanvasBridge( QgsProject::instance()->layerTreeRoot(), mMapCanvas, this );
|
|
|
|
mMapLayerOrder = new QgsCustomLayerOrderWidget( mLayerTreeCanvasBridge, this );
|
|
mMapLayerOrder->setObjectName( QStringLiteral( "theMapLayerOrder" ) );
|
|
|
|
mMapLayerOrder->setWhatsThis( tr( "Map layer list that displays all layers in drawing order." ) );
|
|
mLayerOrderDock = new QgsDockWidget( tr( "Layer Order Panel" ), this );
|
|
mLayerOrderDock->setObjectName( QStringLiteral( "LayerOrder" ) );
|
|
mLayerOrderDock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
|
|
|
|
mLayerOrderDock->setWidget( mMapLayerOrder );
|
|
addDockWidget( Qt::LeftDockWidgetArea, mLayerOrderDock );
|
|
mLayerOrderDock->hide();
|
|
|
|
connect( mMapCanvas, &QgsMapCanvas::mapCanvasRefreshed, this, &QgisApp::updateFilterLegend );
|
|
}
|
|
|
|
void QgisApp::setupLayerTreeViewFromSettings()
|
|
{
|
|
QgsSettings s;
|
|
|
|
QgsLayerTreeModel *model = mLayerTreeView->layerTreeModel();
|
|
model->setFlag( QgsLayerTreeModel::ShowRasterPreviewIcon, s.value( QStringLiteral( "/qgis/createRasterLegendIcons" ), false ).toBool() );
|
|
|
|
QFont fontLayer, fontGroup;
|
|
fontLayer.setBold( s.value( QStringLiteral( "/qgis/legendLayersBold" ), true ).toBool() );
|
|
fontGroup.setBold( s.value( QStringLiteral( "/qgis/legendGroupsBold" ), false ).toBool() );
|
|
model->setLayerTreeNodeFont( QgsLayerTreeNode::NodeLayer, fontLayer );
|
|
model->setLayerTreeNodeFont( QgsLayerTreeNode::NodeGroup, fontGroup );
|
|
}
|
|
|
|
|
|
void QgisApp::updateNewLayerInsertionPoint()
|
|
{
|
|
// defaults
|
|
QgsLayerTreeGroup *parentGroup = mLayerTreeView->layerTreeModel()->rootGroup();
|
|
int index = 0;
|
|
QModelIndex current = mLayerTreeView->currentIndex();
|
|
|
|
if ( current.isValid() )
|
|
{
|
|
if ( QgsLayerTreeNode *currentNode = mLayerTreeView->currentNode() )
|
|
{
|
|
// if the insertion point is actually a group, insert new layers into the group
|
|
if ( QgsLayerTree::isGroup( currentNode ) )
|
|
{
|
|
QgsProject::instance()->layerTreeRegistryBridge()->setLayerInsertionPoint( QgsLayerTree::toGroup( currentNode ), 0 );
|
|
return;
|
|
}
|
|
|
|
// otherwise just set the insertion point in front of the current node
|
|
QgsLayerTreeNode *parentNode = currentNode->parent();
|
|
if ( QgsLayerTree::isGroup( parentNode ) )
|
|
parentGroup = QgsLayerTree::toGroup( parentNode );
|
|
}
|
|
|
|
index = current.row();
|
|
}
|
|
|
|
QgsProject::instance()->layerTreeRegistryBridge()->setLayerInsertionPoint( parentGroup, index );
|
|
}
|
|
|
|
void QgisApp::autoSelectAddedLayer( QList<QgsMapLayer *> layers )
|
|
{
|
|
if ( !layers.isEmpty() )
|
|
{
|
|
QgsLayerTreeLayer *nodeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( layers[0]->id() );
|
|
|
|
if ( !nodeLayer )
|
|
return;
|
|
|
|
QModelIndex index = mLayerTreeView->layerTreeModel()->node2index( nodeLayer );
|
|
mLayerTreeView->setCurrentIndex( index );
|
|
}
|
|
}
|
|
|
|
void QgisApp::createMapTips()
|
|
{
|
|
// Set up the timer for maptips. The timer is reset every time the mouse is moved
|
|
mpMapTipsTimer = new QTimer( mMapCanvas );
|
|
// connect the timer to the maptips slot
|
|
connect( mpMapTipsTimer, &QTimer::timeout, this, &QgisApp::showMapTip );
|
|
// set the interval to 0.850 seconds - timer will be started next time the mouse moves
|
|
mpMapTipsTimer->setInterval( 850 );
|
|
mpMapTipsTimer->setSingleShot( true );
|
|
|
|
// Create the maptips object
|
|
mpMaptip = new QgsMapTip();
|
|
}
|
|
|
|
void QgisApp::createDecorations()
|
|
{
|
|
QgsDecorationCopyright *mDecorationCopyright = new QgsDecorationCopyright( this );
|
|
connect( mActionDecorationCopyright, &QAction::triggered, mDecorationCopyright, &QgsDecorationCopyright::run );
|
|
|
|
QgsDecorationNorthArrow *mDecorationNorthArrow = new QgsDecorationNorthArrow( this );
|
|
connect( mActionDecorationNorthArrow, &QAction::triggered, mDecorationNorthArrow, &QgsDecorationNorthArrow::run );
|
|
|
|
QgsDecorationScaleBar *mDecorationScaleBar = new QgsDecorationScaleBar( this );
|
|
connect( mActionDecorationScaleBar, &QAction::triggered, mDecorationScaleBar, &QgsDecorationScaleBar::run );
|
|
|
|
QgsDecorationGrid *mDecorationGrid = new QgsDecorationGrid( this );
|
|
connect( mActionDecorationGrid, &QAction::triggered, mDecorationGrid, &QgsDecorationGrid::run );
|
|
|
|
QgsDecorationLayoutExtent *decorationLayoutExtent = new QgsDecorationLayoutExtent( this );
|
|
connect( mActionDecorationLayoutExtent, &QAction::triggered, decorationLayoutExtent, &QgsDecorationLayoutExtent::run );
|
|
|
|
// add the decorations in a particular order so they are rendered in that order
|
|
addDecorationItem( mDecorationGrid );
|
|
addDecorationItem( mDecorationCopyright );
|
|
addDecorationItem( mDecorationNorthArrow );
|
|
addDecorationItem( mDecorationScaleBar );
|
|
addDecorationItem( decorationLayoutExtent );
|
|
connect( mMapCanvas, &QgsMapCanvas::renderComplete, this, &QgisApp::renderDecorationItems );
|
|
connect( this, &QgisApp::newProject, this, &QgisApp::projectReadDecorationItems );
|
|
connect( this, &QgisApp::projectRead, this, &QgisApp::projectReadDecorationItems );
|
|
}
|
|
|
|
void QgisApp::renderDecorationItems( QPainter *p )
|
|
{
|
|
QgsRenderContext context = QgsRenderContext::fromMapSettings( mMapCanvas->mapSettings() );
|
|
context.setPainter( p );
|
|
|
|
Q_FOREACH ( QgsDecorationItem *item, mDecorationItems )
|
|
{
|
|
item->render( mMapCanvas->mapSettings(), context );
|
|
}
|
|
}
|
|
|
|
void QgisApp::projectReadDecorationItems()
|
|
{
|
|
Q_FOREACH ( QgsDecorationItem *item, mDecorationItems )
|
|
{
|
|
item->projectRead();
|
|
}
|
|
}
|
|
|
|
// Update project menu with the current list of recently accessed projects
|
|
void QgisApp::updateRecentProjectPaths()
|
|
{
|
|
mRecentProjectsMenu->clear();
|
|
|
|
Q_FOREACH ( const QgsWelcomePageItemsModel::RecentProjectData &recentProject, mRecentProjects )
|
|
{
|
|
QAction *action = mRecentProjectsMenu->addAction( QStringLiteral( "%1 (%2)" ).arg( recentProject.title != recentProject.path ? recentProject.title : QFileInfo( recentProject.path ).baseName(),
|
|
QDir::toNativeSeparators( recentProject.path ) ) );
|
|
//action->setEnabled( QFile::exists( ( recentProject.path ) ) );
|
|
action->setData( recentProject.path );
|
|
}
|
|
|
|
if ( mWelcomePage )
|
|
mWelcomePage->setRecentProjects( mRecentProjects );
|
|
|
|
#if defined(Q_OS_WIN)
|
|
QWinJumpList jumplist;
|
|
jumplist.recent()->clear();
|
|
Q_FOREACH ( const QgsWelcomePageItemsModel::RecentProjectData &recentProject, mRecentProjects )
|
|
{
|
|
QString name = recentProject.title != recentProject.path ? recentProject.title : QFileInfo( recentProject.path ).baseName();
|
|
QWinJumpListItem *newProject = new QWinJumpListItem( QWinJumpListItem::Link );
|
|
newProject->setTitle( name );
|
|
newProject->setFilePath( QDir::toNativeSeparators( QCoreApplication::applicationFilePath() ) );
|
|
newProject->setArguments( QStringList( recentProject.path ) );
|
|
jumplist.recent()->addItem( newProject );
|
|
}
|
|
#endif
|
|
|
|
} // QgisApp::updateRecentProjectPaths
|
|
|
|
// add this file to the recently opened/saved projects list
|
|
void QgisApp::saveRecentProjectPath( const QString &projectPath, bool savePreviewImage )
|
|
{
|
|
// first, re-read the recent project paths. This prevents loss of recent
|
|
// projects when multiple QGIS sessions are open
|
|
readRecentProjects();
|
|
|
|
QgsSettings settings;
|
|
|
|
// Get canonical absolute path
|
|
QFileInfo myFileInfo( projectPath );
|
|
QgsWelcomePageItemsModel::RecentProjectData projectData;
|
|
projectData.path = myFileInfo.absoluteFilePath();
|
|
projectData.title = QgsProject::instance()->title();
|
|
if ( projectData.title.isEmpty() )
|
|
projectData.title = projectData.path;
|
|
|
|
projectData.crs = QgsProject::instance()->crs().authid();
|
|
|
|
if ( savePreviewImage )
|
|
{
|
|
// Generate a unique file name
|
|
QString fileName( QCryptographicHash::hash( ( projectData.path.toUtf8() ), QCryptographicHash::Md5 ).toHex() );
|
|
QString previewDir = QStringLiteral( "%1/previewImages" ).arg( QgsApplication::qgisSettingsDirPath() );
|
|
projectData.previewImagePath = QStringLiteral( "%1/%2.png" ).arg( previewDir, fileName );
|
|
QDir().mkdir( previewDir );
|
|
|
|
// Render the map canvas
|
|
QSize previewSize( 250, 177 ); // h = w / std::sqrt(2)
|
|
QRect previewRect( QPoint( ( mMapCanvas->width() - previewSize.width() ) / 2
|
|
, ( mMapCanvas->height() - previewSize.height() ) / 2 )
|
|
, previewSize );
|
|
|
|
QPixmap previewImage( previewSize );
|
|
QPainter previewPainter( &previewImage );
|
|
mMapCanvas->render( &previewPainter, QRect( QPoint(), previewSize ), previewRect );
|
|
|
|
// Save
|
|
previewImage.save( projectData.previewImagePath );
|
|
}
|
|
else
|
|
{
|
|
int idx = mRecentProjects.indexOf( projectData );
|
|
if ( idx != -1 )
|
|
projectData.previewImagePath = mRecentProjects.at( idx ).previewImagePath;
|
|
}
|
|
|
|
// If this file is already in the list, remove it
|
|
mRecentProjects.removeAll( projectData );
|
|
|
|
// Prepend this file to the list
|
|
mRecentProjects.prepend( projectData );
|
|
|
|
// Keep the list to 10 items by trimming excess off the bottom
|
|
// And remove the associated image
|
|
while ( mRecentProjects.count() > 10 )
|
|
{
|
|
QFile( mRecentProjects.takeLast().previewImagePath ).remove();
|
|
}
|
|
|
|
settings.remove( QStringLiteral( "/UI/recentProjects" ) );
|
|
int idx = 0;
|
|
|
|
// Persist the list
|
|
Q_FOREACH ( const QgsWelcomePageItemsModel::RecentProjectData &recentProject, mRecentProjects )
|
|
{
|
|
++idx;
|
|
settings.beginGroup( QStringLiteral( "UI/recentProjects/%1" ).arg( idx ) );
|
|
settings.setValue( QStringLiteral( "title" ), recentProject.title );
|
|
settings.setValue( QStringLiteral( "path" ), recentProject.path );
|
|
settings.setValue( QStringLiteral( "previewImage" ), recentProject.previewImagePath );
|
|
settings.setValue( QStringLiteral( "crs" ), recentProject.crs );
|
|
settings.endGroup();
|
|
}
|
|
|
|
// Update menu list of paths
|
|
updateRecentProjectPaths();
|
|
|
|
} // QgisApp::saveRecentProjectPath
|
|
|
|
// Update project menu with the project templates
|
|
void QgisApp::updateProjectFromTemplates()
|
|
{
|
|
// get list of project files in template dir
|
|
QgsSettings settings;
|
|
QString templateDirName = settings.value( QStringLiteral( "qgis/projectTemplateDir" ),
|
|
QgsApplication::qgisSettingsDirPath() + "project_templates" ).toString();
|
|
QDir templateDir( templateDirName );
|
|
QStringList filters( QStringLiteral( "*.qgs" ) );
|
|
templateDir.setNameFilters( filters );
|
|
QStringList templateFiles = templateDir.entryList( filters );
|
|
|
|
// Remove existing entries
|
|
mProjectFromTemplateMenu->clear();
|
|
|
|
// Add entries
|
|
Q_FOREACH ( const QString &templateFile, templateFiles )
|
|
{
|
|
mProjectFromTemplateMenu->addAction( templateFile );
|
|
}
|
|
|
|
// add <blank> entry, which loads a blank template (regardless of "default template")
|
|
if ( settings.value( QStringLiteral( "qgis/newProjectDefault" ), QVariant( false ) ).toBool() )
|
|
mProjectFromTemplateMenu->addAction( tr( "< Blank >" ) );
|
|
|
|
} // QgisApp::updateProjectFromTemplates
|
|
|
|
void QgisApp::saveWindowState()
|
|
{
|
|
// store window and toolbar positions
|
|
QgsSettings settings;
|
|
// store the toolbar/dock widget settings using Qt4 settings API
|
|
settings.setValue( QStringLiteral( "UI/state" ), saveState() );
|
|
|
|
// store window geometry
|
|
settings.setValue( QStringLiteral( "UI/geometry" ), saveGeometry() );
|
|
|
|
QgsPluginRegistry::instance()->unloadAll();
|
|
}
|
|
|
|
#include "ui_defaults.h"
|
|
|
|
void QgisApp::restoreWindowState()
|
|
{
|
|
// restore the toolbar and dock widgets positions using Qt4 settings API
|
|
QgsSettings settings;
|
|
|
|
if ( !restoreState( settings.value( QStringLiteral( "UI/state" ), QByteArray::fromRawData( reinterpret_cast< const char * >( defaultUIstate ), sizeof defaultUIstate ) ).toByteArray() ) )
|
|
{
|
|
QgsDebugMsg( "restore of UI state failed" );
|
|
}
|
|
|
|
// restore window geometry
|
|
if ( !restoreGeometry( settings.value( QStringLiteral( "UI/geometry" ) ).toByteArray() ) )
|
|
{
|
|
QgsDebugMsg( "restore of UI geometry failed" );
|
|
// default to 80% of screen size, at 10% from top left corner
|
|
resize( QDesktopWidget().availableGeometry( this ).size() * 0.8 );
|
|
QSize pos = QDesktopWidget().availableGeometry( this ).size() * 0.1;
|
|
move( pos.width(), pos.height() );
|
|
}
|
|
|
|
}
|
|
///////////// END OF GUI SETUP ROUTINES ///////////////
|
|
void QgisApp::sponsors()
|
|
{
|
|
QgsSettings settings;
|
|
QString qgisSponsorsUrl = settings.value( QStringLiteral( "qgis/qgisSponsorsUrl" ),
|
|
tr( "http://qgis.org/en/site/about/sponsorship.html" ) ).toString();
|
|
openURL( qgisSponsorsUrl, false );
|
|
}
|
|
|
|
void QgisApp::about()
|
|
{
|
|
static QgsAbout *sAbt = nullptr;
|
|
if ( !sAbt )
|
|
{
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
sAbt = new QgsAbout( this );
|
|
QString versionString = QStringLiteral( "<html><body><div align='center'><table width='100%'>" );
|
|
|
|
versionString += QLatin1String( "<tr>" );
|
|
versionString += "<td>" + tr( "QGIS version" ) + "</td><td>" + Qgis::QGIS_VERSION + "</td><td>";
|
|
|
|
|
|
if ( QString( Qgis::QGIS_DEV_VERSION ) == QLatin1String( "exported" ) )
|
|
{
|
|
versionString += tr( "QGIS code branch" ) + QStringLiteral( "</td><td><a href=\"https://github.com/qgis/QGIS/tree/release-%1_%2\">Release %1.%2</a></td>" )
|
|
.arg( Qgis::QGIS_VERSION_INT / 10000 ).arg( Qgis::QGIS_VERSION_INT / 100 % 100 );
|
|
}
|
|
else
|
|
{
|
|
versionString += tr( "QGIS code revision" ) + QStringLiteral( "</td><td><a href=\"https://github.com/qgis/QGIS/commit/%1\">%1</a></td>" ).arg( Qgis::QGIS_DEV_VERSION );
|
|
}
|
|
|
|
versionString += QLatin1String( "</tr><tr>" );
|
|
|
|
versionString += "<td>" + tr( "Compiled against Qt" ) + "</td><td>" + QT_VERSION_STR + "</td>";
|
|
versionString += "<td>" + tr( "Running against Qt" ) + "</td><td>" + qVersion() + "</td>";
|
|
|
|
versionString += QLatin1String( "</tr><tr>" );
|
|
|
|
versionString += "<td>" + tr( "Compiled against GDAL/OGR" ) + "</td><td>" + GDAL_RELEASE_NAME + "</td>";
|
|
versionString += "<td>" + tr( "Running against GDAL/OGR" ) + "</td><td>" + GDALVersionInfo( "RELEASE_NAME" ) + "</td>";
|
|
|
|
versionString += QLatin1String( "</tr><tr>" );
|
|
|
|
versionString += "<td>" + tr( "Compiled against GEOS" ) + "</td><td>" + GEOS_CAPI_VERSION + "</td>";
|
|
versionString += "<td>" + tr( "Running against GEOS" ) + "</td><td>" + GEOSversion() + "</td>";
|
|
|
|
versionString += QLatin1String( "</tr><tr>" );
|
|
|
|
versionString += "<td>" + tr( "PostgreSQL Client Version" ) + "</td><td>";
|
|
#ifdef HAVE_POSTGRESQL
|
|
versionString += PG_VERSION;
|
|
#else
|
|
versionString += tr( "No support." );
|
|
#endif
|
|
versionString += QLatin1String( "</td>" );
|
|
|
|
versionString += "<td>" + tr( "SpatiaLite Version" ) + "</td><td>";
|
|
versionString += spatialite_version();
|
|
versionString += QLatin1String( "</td>" );
|
|
|
|
versionString += QLatin1String( "</tr><tr>" );
|
|
|
|
versionString += "<td>" + tr( "QWT Version" ) + "</td><td>" + QWT_VERSION_STR + "</td>";
|
|
versionString += "<td>" + tr( "PROJ.4 Version" ) + "</td><td>" + QString::number( PJ_VERSION ) + "</td>";
|
|
|
|
versionString += QLatin1String( "</tr><tr>" );
|
|
|
|
versionString += "<td>" + tr( "QScintilla2 Version" ) + "</td><td>" + QSCINTILLA_VERSION_STR + "</td>";
|
|
|
|
#ifdef QGISDEBUG
|
|
versionString += "<td colspan=2>" + tr( "This copy of QGIS writes debugging output." ) + "</td>";
|
|
#endif
|
|
|
|
versionString += QLatin1String( "</tr></table></div></body></html>" );
|
|
|
|
sAbt->setVersion( versionString );
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
}
|
|
sAbt->show();
|
|
sAbt->raise();
|
|
sAbt->activateWindow();
|
|
}
|
|
|
|
void QgisApp::addLayerDefinition()
|
|
{
|
|
QString path = QFileDialog::getOpenFileName( this, QStringLiteral( "Add Layer Definition File" ), QDir::home().path(), QStringLiteral( "*.qlr" ) );
|
|
if ( path.isEmpty() )
|
|
return;
|
|
|
|
openLayerDefinition( path );
|
|
}
|
|
|
|
QString QgisApp::crsAndFormatAdjustedLayerUri( const QString &uri, const QStringList &supportedCrs, const QStringList &supportedFormats ) const
|
|
{
|
|
QString newuri = uri;
|
|
|
|
// Adjust layer CRS to project CRS
|
|
QgsCoordinateReferenceSystem testCrs;
|
|
Q_FOREACH ( const QString &c, supportedCrs )
|
|
{
|
|
testCrs.createFromOgcWmsCrs( c );
|
|
if ( testCrs == mMapCanvas->mapSettings().destinationCrs() )
|
|
{
|
|
newuri.replace( QRegExp( "crs=[^&]+" ), "crs=" + c );
|
|
QgsDebugMsg( QString( "Changing layer crs to %1, new uri: %2" ).arg( c, uri ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Use the last used image format
|
|
QString lastImageEncoding = QgsSettings().value( QStringLiteral( "/qgis/lastWmsImageEncoding" ), "image/png" ).toString();
|
|
Q_FOREACH ( const QString &fmt, supportedFormats )
|
|
{
|
|
if ( fmt == lastImageEncoding )
|
|
{
|
|
newuri.replace( QRegExp( "format=[^&]+" ), "format=" + fmt );
|
|
QgsDebugMsg( QString( "Changing layer format to %1, new uri: %2" ).arg( fmt, uri ) );
|
|
break;
|
|
}
|
|
}
|
|
return newuri;
|
|
}
|
|
|
|
bool QgisApp::addVectorLayers( const QStringList &layerQStringList, const QString &enc, const QString &dataSourceType )
|
|
{
|
|
bool wasfrozen = mMapCanvas->isFrozen();
|
|
QList<QgsMapLayer *> myList;
|
|
Q_FOREACH ( QString src, layerQStringList )
|
|
{
|
|
src = src.trimmed();
|
|
QString base;
|
|
if ( dataSourceType == QLatin1String( "file" ) )
|
|
{
|
|
QString srcWithoutLayername( src );
|
|
int posPipe = srcWithoutLayername.indexOf( '|' );
|
|
if ( posPipe >= 0 )
|
|
srcWithoutLayername.resize( posPipe );
|
|
QFileInfo fi( srcWithoutLayername );
|
|
base = fi.completeBaseName();
|
|
|
|
// if needed prompt for zipitem layers
|
|
QString vsiPrefix = QgsZipItem::vsiPrefix( src );
|
|
if ( ! src.startsWith( QLatin1String( "/vsi" ), Qt::CaseInsensitive ) &&
|
|
( vsiPrefix == QLatin1String( "/vsizip/" ) || vsiPrefix == QLatin1String( "/vsitar/" ) ) )
|
|
{
|
|
if ( askUserForZipItemLayers( src ) )
|
|
continue;
|
|
}
|
|
}
|
|
else if ( dataSourceType == QLatin1String( "database" ) )
|
|
{
|
|
base = src;
|
|
}
|
|
else //directory //protocol
|
|
{
|
|
QFileInfo fi( src );
|
|
base = fi.completeBaseName();
|
|
}
|
|
|
|
QgsDebugMsg( "completeBaseName: " + base );
|
|
|
|
// create the layer
|
|
|
|
QgsVectorLayer *layer = new QgsVectorLayer( src, base, QStringLiteral( "ogr" ), false );
|
|
Q_CHECK_PTR( layer );
|
|
|
|
if ( ! layer )
|
|
{
|
|
freezeCanvases( false );
|
|
|
|
// Let render() do its own cursor management
|
|
// QApplication::restoreOverrideCursor();
|
|
|
|
// XXX insert meaningful whine to the user here
|
|
return false;
|
|
}
|
|
|
|
if ( layer->isValid() )
|
|
{
|
|
layer->setProviderEncoding( enc );
|
|
|
|
QStringList sublayers = layer->dataProvider()->subLayers();
|
|
QgsDebugMsg( QString( "got valid layer with %1 sublayers" ).arg( sublayers.count() ) );
|
|
|
|
// If the newly created layer has more than 1 layer of data available, we show the
|
|
// sublayers selection dialog so the user can select the sublayers to actually load.
|
|
if ( sublayers.count() > 1 )
|
|
{
|
|
askUserForOGRSublayers( layer );
|
|
|
|
// The first layer loaded is not useful in that case. The user can select it in
|
|
// the list if he wants to load it.
|
|
delete layer;
|
|
|
|
}
|
|
else if ( !sublayers.isEmpty() ) // there is 1 layer of data available
|
|
{
|
|
//set friendly name for datasources with only one layer
|
|
QStringList elements = sublayers.at( 0 ).split( ':' );
|
|
|
|
if ( elements.size() >= 4 && layer->name() != elements.at( 1 ) )
|
|
{
|
|
layer->setName( QStringLiteral( "%1 %2 %3" ).arg( layer->name(), elements.at( 1 ), elements.at( 3 ) ) );
|
|
}
|
|
|
|
myList << layer;
|
|
}
|
|
else
|
|
{
|
|
QString msg = tr( "%1 doesn't have any layers" ).arg( src );
|
|
messageBar()->pushMessage( tr( "Invalid Data Source" ), msg, QgsMessageBar::CRITICAL, messageTimeout() );
|
|
delete layer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QString msg = tr( "%1 is not a valid or recognized data source" ).arg( src );
|
|
messageBar()->pushMessage( tr( "Invalid Data Source" ), msg, QgsMessageBar::CRITICAL, messageTimeout() );
|
|
|
|
// since the layer is bad, stomp on it
|
|
delete layer;
|
|
}
|
|
|
|
}
|
|
|
|
// make sure at least one layer was successfully added
|
|
if ( myList.isEmpty() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Register this layer with the layers registry
|
|
QgsProject::instance()->addMapLayers( myList );
|
|
Q_FOREACH ( QgsMapLayer *l, myList )
|
|
{
|
|
bool ok;
|
|
l->loadDefaultStyle( ok );
|
|
}
|
|
activateDeactivateLayerRelatedActions( activeLayer() );
|
|
|
|
// Only update the map if we frozen in this method
|
|
// Let the caller do it otherwise
|
|
if ( !wasfrozen )
|
|
{
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
}
|
|
// Let render() do its own cursor management
|
|
// QApplication::restoreOverrideCursor();
|
|
|
|
// statusBar()->showMessage( mMapCanvas->extent().toString( 2 ) );
|
|
|
|
return true;
|
|
} // QgisApp::addVectorLayer()
|
|
|
|
// present a dialog to choose zipitem layers
|
|
bool QgisApp::askUserForZipItemLayers( const QString &path )
|
|
{
|
|
bool ok = false;
|
|
QVector<QgsDataItem *> childItems;
|
|
QgsZipItem *zipItem = nullptr;
|
|
QgsSettings settings;
|
|
int promptLayers = settings.value( QStringLiteral( "qgis/promptForRasterSublayers" ), 1 ).toInt();
|
|
|
|
QgsDebugMsg( "askUserForZipItemLayers( " + path + ')' );
|
|
|
|
// if scanZipBrowser == no: skip to the next file
|
|
if ( settings.value( QStringLiteral( "qgis/scanZipInBrowser2" ), "basic" ).toString() == QLatin1String( "no" ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
zipItem = new QgsZipItem( nullptr, path, path );
|
|
if ( ! zipItem )
|
|
return false;
|
|
|
|
zipItem->populate();
|
|
QgsDebugMsg( QString( "Path= %1 got zipitem with %2 children" ).arg( path ).arg( zipItem->rowCount() ) );
|
|
|
|
// if 1 or 0 child found, exit so a normal item is created by gdal or ogr provider
|
|
if ( zipItem->rowCount() <= 1 )
|
|
{
|
|
delete zipItem;
|
|
return false;
|
|
}
|
|
|
|
// if promptLayers=Load all, load all layers without prompting
|
|
if ( promptLayers == 3 )
|
|
{
|
|
childItems = zipItem->children();
|
|
}
|
|
// exit if promptLayers=Never
|
|
else if ( promptLayers == 2 )
|
|
{
|
|
delete zipItem;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// We initialize a selection dialog and display it.
|
|
QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Vsifile, QStringLiteral( "vsi" ), this );
|
|
QgsSublayersDialog::LayerDefinitionList layers;
|
|
|
|
for ( int i = 0; i < zipItem->children().size(); i++ )
|
|
{
|
|
QgsDataItem *item = zipItem->children().at( i );
|
|
QgsLayerItem *layerItem = dynamic_cast<QgsLayerItem *>( item );
|
|
if ( !layerItem )
|
|
continue;
|
|
|
|
QgsDebugMsgLevel( QString( "item path=%1 provider=%2" ).arg( item->path(), layerItem->providerKey() ), 2 );
|
|
|
|
QgsSublayersDialog::LayerDefinition def;
|
|
def.layerId = i;
|
|
def.layerName = item->name();
|
|
if ( layerItem->providerKey() == QLatin1String( "gdal" ) )
|
|
{
|
|
def.type = tr( "Raster" );
|
|
}
|
|
else if ( layerItem->providerKey() == QLatin1String( "ogr" ) )
|
|
{
|
|
def.type = tr( "Vector" );
|
|
}
|
|
layers << def;
|
|
}
|
|
|
|
chooseSublayersDialog.populateLayerTable( layers );
|
|
|
|
if ( chooseSublayersDialog.exec() )
|
|
{
|
|
Q_FOREACH ( const QgsSublayersDialog::LayerDefinition &def, chooseSublayersDialog.selection() )
|
|
{
|
|
childItems << zipItem->children().at( def.layerId );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( childItems.isEmpty() )
|
|
{
|
|
// return true so dialog doesn't popup again (#6225) - hopefully this doesn't create other trouble
|
|
ok = true;
|
|
}
|
|
|
|
// add childItems
|
|
Q_FOREACH ( QgsDataItem *item, childItems )
|
|
{
|
|
QgsLayerItem *layerItem = dynamic_cast<QgsLayerItem *>( item );
|
|
if ( !layerItem )
|
|
continue;
|
|
|
|
QgsDebugMsg( QString( "item path=%1 provider=%2" ).arg( item->path(), layerItem->providerKey() ) );
|
|
if ( layerItem->providerKey() == QLatin1String( "gdal" ) )
|
|
{
|
|
if ( addRasterLayer( item->path(), QFileInfo( item->name() ).completeBaseName() ) )
|
|
ok = true;
|
|
}
|
|
else if ( layerItem->providerKey() == QLatin1String( "ogr" ) )
|
|
{
|
|
if ( addVectorLayers( QStringList( item->path() ), QStringLiteral( "System" ), QStringLiteral( "file" ) ) )
|
|
ok = true;
|
|
}
|
|
}
|
|
|
|
delete zipItem;
|
|
return ok;
|
|
}
|
|
|
|
// present a dialog to choose GDAL raster sublayers
|
|
void QgisApp::askUserForGDALSublayers( QgsRasterLayer *layer )
|
|
{
|
|
if ( !layer )
|
|
return;
|
|
|
|
QStringList sublayers = layer->subLayers();
|
|
QgsDebugMsg( QString( "raster has %1 sublayers" ).arg( layer->subLayers().size() ) );
|
|
|
|
if ( sublayers.empty() )
|
|
return;
|
|
|
|
// if promptLayers=Load all, load all sublayers without prompting
|
|
QgsSettings settings;
|
|
if ( settings.value( QStringLiteral( "qgis/promptForRasterSublayers" ), 1 ).toInt() == 3 )
|
|
{
|
|
loadGDALSublayers( layer->source(), sublayers );
|
|
return;
|
|
}
|
|
|
|
// We initialize a selection dialog and display it.
|
|
QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Gdal, QStringLiteral( "gdal" ), this );
|
|
chooseSublayersDialog.setShowAddToGroupCheckbox( true );
|
|
|
|
QgsSublayersDialog::LayerDefinitionList layers;
|
|
QStringList names;
|
|
names.reserve( sublayers.size() );
|
|
layers.reserve( sublayers.size() );
|
|
for ( int i = 0; i < sublayers.size(); i++ )
|
|
{
|
|
// simplify raster sublayer name - should add a function in gdal provider for this?
|
|
// code is copied from QgsGdalLayerItem::createChildren
|
|
QString name = sublayers[i];
|
|
QString path = layer->source();
|
|
// if netcdf/hdf use all text after filename
|
|
// for hdf4 it would be best to get description, because the subdataset_index is not very practical
|
|
if ( name.startsWith( QLatin1String( "netcdf" ), Qt::CaseInsensitive ) ||
|
|
name.startsWith( QLatin1String( "hdf" ), Qt::CaseInsensitive ) )
|
|
name = name.mid( name.indexOf( path ) + path.length() + 1 );
|
|
else
|
|
{
|
|
// remove driver name and file name
|
|
name.remove( name.split( ':' )[0] );
|
|
name.remove( path );
|
|
}
|
|
// remove any : or " left over
|
|
if ( name.startsWith( ':' ) )
|
|
name.remove( 0, 1 );
|
|
|
|
if ( name.startsWith( '\"' ) )
|
|
name.remove( 0, 1 );
|
|
|
|
if ( name.endsWith( ':' ) )
|
|
name.chop( 1 );
|
|
|
|
if ( name.endsWith( '\"' ) )
|
|
name.chop( 1 );
|
|
|
|
names << name;
|
|
|
|
QgsSublayersDialog::LayerDefinition def;
|
|
def.layerId = i;
|
|
def.layerName = name;
|
|
layers << def;
|
|
}
|
|
|
|
chooseSublayersDialog.populateLayerTable( layers );
|
|
|
|
if ( chooseSublayersDialog.exec() )
|
|
{
|
|
// create more informative layer names, containing filename as well as sublayer name
|
|
QRegExp rx( "\"(.*)\"" );
|
|
QString uri, name;
|
|
|
|
QgsLayerTreeGroup *group = nullptr;
|
|
bool addToGroup = settings.value( QStringLiteral( "/qgis/openSublayersInGroup" ), true ).toBool();
|
|
if ( addToGroup )
|
|
{
|
|
group = QgsProject::instance()->layerTreeRoot()->insertGroup( 0, layer->name() );
|
|
}
|
|
|
|
Q_FOREACH ( const QgsSublayersDialog::LayerDefinition &def, chooseSublayersDialog.selection() )
|
|
{
|
|
int i = def.layerId;
|
|
if ( rx.indexIn( sublayers[i] ) != -1 )
|
|
{
|
|
uri = rx.cap( 1 );
|
|
name = sublayers[i];
|
|
name.replace( uri, QFileInfo( uri ).completeBaseName() );
|
|
}
|
|
else
|
|
{
|
|
name = names[i];
|
|
}
|
|
|
|
QgsRasterLayer *rlayer = new QgsRasterLayer( sublayers[i], name );
|
|
if ( rlayer && rlayer->isValid() )
|
|
{
|
|
if ( addToGroup )
|
|
{
|
|
QgsProject::instance()->addMapLayer( rlayer, false );
|
|
group->addLayer( rlayer );
|
|
}
|
|
else
|
|
{
|
|
addRasterLayer( rlayer );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// should the GDAL sublayers dialog should be presented to the user?
|
|
bool QgisApp::shouldAskUserForGDALSublayers( QgsRasterLayer *layer )
|
|
{
|
|
// return false if layer is empty or raster has no sublayers
|
|
if ( !layer || layer->providerType() != QLatin1String( "gdal" ) || layer->subLayers().empty() )
|
|
return false;
|
|
|
|
QgsSettings settings;
|
|
int promptLayers = settings.value( QStringLiteral( "qgis/promptForRasterSublayers" ), 1 ).toInt();
|
|
// 0 = Always -> always ask (if there are existing sublayers)
|
|
// 1 = If needed -> ask if layer has no bands, but has sublayers
|
|
// 2 = Never -> never prompt, will not load anything
|
|
// 3 = Load all -> never prompt, but load all sublayers
|
|
|
|
return promptLayers == 0 || promptLayers == 3 || ( promptLayers == 1 && layer->bandCount() == 0 );
|
|
}
|
|
|
|
// This method will load with GDAL the layers in parameter.
|
|
// It is normally triggered by the sublayer selection dialog.
|
|
void QgisApp::loadGDALSublayers( const QString &uri, const QStringList &list )
|
|
{
|
|
QString path, name;
|
|
QgsRasterLayer *subLayer = nullptr;
|
|
QgsSettings settings;
|
|
QgsLayerTreeGroup *group = nullptr;
|
|
bool addToGroup = settings.value( QStringLiteral( "/qgis/openSublayersInGroup" ), true ).toBool();
|
|
if ( addToGroup )
|
|
group = QgsProject::instance()->layerTreeRoot()->insertGroup( 0, QFileInfo( uri ).completeBaseName() );
|
|
|
|
//add layers in reverse order so they appear in the right order in the layer dock
|
|
for ( int i = list.size() - 1; i >= 0 ; i-- )
|
|
{
|
|
path = list[i];
|
|
// shorten name by replacing complete path with filename
|
|
name = path;
|
|
name.replace( uri, QFileInfo( uri ).completeBaseName() );
|
|
subLayer = new QgsRasterLayer( path, name );
|
|
if ( subLayer )
|
|
{
|
|
if ( subLayer->isValid() )
|
|
if ( addToGroup )
|
|
{
|
|
QgsProject::instance()->addMapLayer( subLayer, false );
|
|
group->addLayer( subLayer );
|
|
}
|
|
else
|
|
{
|
|
addRasterLayer( subLayer );
|
|
}
|
|
else
|
|
delete subLayer;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// This method is the method that does the real job. If the layer given in
|
|
// parameter is nullptr, then the method tries to act on the activeLayer.
|
|
void QgisApp::askUserForOGRSublayers( QgsVectorLayer *layer )
|
|
{
|
|
if ( !layer )
|
|
{
|
|
layer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( !layer || layer->dataProvider()->name() != QLatin1String( "ogr" ) )
|
|
return;
|
|
}
|
|
|
|
QStringList sublayers = layer->dataProvider()->subLayers();
|
|
|
|
QgsSublayersDialog::LayerDefinitionList list;
|
|
Q_FOREACH ( const QString &sublayer, sublayers )
|
|
{
|
|
// OGR provider returns items in this format:
|
|
// <layer_index>:<name>:<feature_count>:<geom_type>
|
|
|
|
QStringList elements = sublayer.split( QStringLiteral( ":" ) );
|
|
// merge back parts of the name that may have been split
|
|
while ( elements.size() > 4 )
|
|
{
|
|
elements[1] += ":" + elements[2];
|
|
elements.removeAt( 2 );
|
|
}
|
|
|
|
if ( elements.count() == 4 )
|
|
{
|
|
QgsSublayersDialog::LayerDefinition def;
|
|
def.layerId = elements[0].toInt();
|
|
def.layerName = elements[1];
|
|
def.count = elements[2].toInt();
|
|
def.type = elements[3];
|
|
list << def;
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( "Unexpected output from OGR provider's subLayers()! " + sublayer );
|
|
}
|
|
}
|
|
|
|
// Check if the current layer uri contains the
|
|
|
|
// We initialize a selection dialog and display it.
|
|
QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Ogr, QStringLiteral( "ogr" ), this );
|
|
chooseSublayersDialog.setShowAddToGroupCheckbox( true );
|
|
chooseSublayersDialog.populateLayerTable( list );
|
|
|
|
if ( !chooseSublayersDialog.exec() )
|
|
return;
|
|
|
|
QString uri = layer->source();
|
|
QString name = layer->name();
|
|
//the separator char & was changed to | to be compatible
|
|
//with url for protocol drivers
|
|
if ( uri.contains( '|', Qt::CaseSensitive ) )
|
|
{
|
|
// If we get here, there are some options added to the filename.
|
|
// A valid uri is of the form: filename&option1=value1&option2=value2,...
|
|
// We want only the filename here, so we get the first part of the split.
|
|
QStringList theURIParts = uri.split( '|' );
|
|
uri = theURIParts.at( 0 );
|
|
}
|
|
|
|
// The uri must contain the actual uri of the vectorLayer from which we are
|
|
// going to load the sublayers.
|
|
QString fileName = QFileInfo( uri ).baseName();
|
|
QList<QgsMapLayer *> myList;
|
|
Q_FOREACH ( const QgsSublayersDialog::LayerDefinition &def, chooseSublayersDialog.selection() )
|
|
{
|
|
QString layerGeometryType = def.type;
|
|
QString composedURI = uri + "|layerid=" + QString::number( def.layerId );
|
|
|
|
if ( !layerGeometryType.isEmpty() )
|
|
{
|
|
composedURI += "|geometrytype=" + layerGeometryType;
|
|
}
|
|
|
|
QgsDebugMsg( "Creating new vector layer using " + composedURI );
|
|
QString name = fileName + " " + def.layerName;
|
|
if ( !layerGeometryType.isEmpty() )
|
|
name += " " + layerGeometryType;
|
|
QgsVectorLayer *layer = new QgsVectorLayer( composedURI, name, QStringLiteral( "ogr" ), false );
|
|
if ( layer && layer->isValid() )
|
|
{
|
|
myList << layer;
|
|
}
|
|
else
|
|
{
|
|
QString msg = tr( "%1 is not a valid or recognized data source" ).arg( composedURI );
|
|
messageBar()->pushMessage( tr( "Invalid Data Source" ), msg, QgsMessageBar::CRITICAL, messageTimeout() );
|
|
delete layer;
|
|
}
|
|
}
|
|
|
|
if ( ! myList.isEmpty() )
|
|
{
|
|
QgsSettings settings;
|
|
bool addToGroup = settings.value( QStringLiteral( "/qgis/openSublayersInGroup" ), true ).toBool();
|
|
QgsLayerTreeGroup *group = nullptr;
|
|
if ( addToGroup )
|
|
group = QgsProject::instance()->layerTreeRoot()->insertGroup( 0, name );
|
|
|
|
QgsProject::instance()->addMapLayers( myList, ! addToGroup );
|
|
Q_FOREACH ( QgsMapLayer *l, myList )
|
|
{
|
|
bool ok;
|
|
l->loadDefaultStyle( ok );
|
|
if ( addToGroup )
|
|
group->addLayer( l );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::addDatabaseLayer()
|
|
{
|
|
#ifdef HAVE_POSTGRESQL
|
|
// Fudge for now
|
|
QgsDebugMsg( "about to addRasterLayer" );
|
|
|
|
// TODO: QDialog for now, switch to QWidget in future
|
|
QDialog *dbs = dynamic_cast<QDialog *>( QgsProviderRegistry::instance()->createSelectionWidget( QStringLiteral( "postgres" ), this ) );
|
|
if ( !dbs )
|
|
{
|
|
QMessageBox::warning( this, tr( "PostgreSQL" ), tr( "Cannot get PostgreSQL select dialog from provider." ) );
|
|
return;
|
|
}
|
|
connect( dbs, SIGNAL( addDatabaseLayers( QStringList const &, QString const & ) ),
|
|
this, SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) );
|
|
connect( dbs, SIGNAL( progress( int, int ) ),
|
|
this, SLOT( showProgress( int, int ) ) );
|
|
connect( dbs, SIGNAL( progressMessage( QString ) ),
|
|
this, SLOT( showStatusMessage( QString ) ) );
|
|
dbs->exec();
|
|
delete dbs;
|
|
#endif
|
|
} // QgisApp::addDatabaseLayer()
|
|
|
|
void QgisApp::addDatabaseLayers( QStringList const &layerPathList, QString const &providerKey )
|
|
{
|
|
QList<QgsMapLayer *> myList;
|
|
|
|
if ( layerPathList.empty() )
|
|
{
|
|
// no layers to add so bail out, but
|
|
// allow mMapCanvas to handle events
|
|
// first
|
|
freezeCanvases( false );
|
|
return;
|
|
}
|
|
|
|
freezeCanvases( true );
|
|
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
|
|
Q_FOREACH ( const QString &layerPath, layerPathList )
|
|
{
|
|
// create the layer
|
|
QgsDataSourceUri uri( layerPath );
|
|
|
|
QgsVectorLayer *layer = new QgsVectorLayer( uri.uri( false ), uri.table(), providerKey, false );
|
|
Q_CHECK_PTR( layer );
|
|
|
|
if ( ! layer )
|
|
{
|
|
freezeCanvases( false );
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
// XXX insert meaningful whine to the user here
|
|
return;
|
|
}
|
|
|
|
if ( layer->isValid() )
|
|
{
|
|
// add to list of layers to register
|
|
//with the central layers registry
|
|
myList << layer;
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( tr( "%1 is an invalid layer - not loaded" ).arg( layerPath ) );
|
|
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() );
|
|
msgLabel->setWordWrap( true );
|
|
connect( msgLabel, &QLabel::linkActivated, mLogDock, &QWidget::show );
|
|
QgsMessageBarItem *item = new QgsMessageBarItem( msgLabel, QgsMessageBar::WARNING );
|
|
messageBar()->pushItem( item );
|
|
delete layer;
|
|
}
|
|
//qWarning("incrementing iterator");
|
|
}
|
|
|
|
QgsProject::instance()->addMapLayers( myList );
|
|
|
|
// load default style after adding to process readCustomSymbology signals
|
|
Q_FOREACH ( QgsMapLayer *l, myList )
|
|
{
|
|
bool ok;
|
|
l->loadDefaultStyle( ok );
|
|
}
|
|
|
|
// draw the map
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
}
|
|
|
|
|
|
void QgisApp::addSpatiaLiteLayer()
|
|
{
|
|
// show the SpatiaLite dialog
|
|
QDialog *dbs = dynamic_cast<QDialog *>( QgsProviderRegistry::instance()->createSelectionWidget( QStringLiteral( "spatialite" ), this ) );
|
|
if ( !dbs )
|
|
{
|
|
QMessageBox::warning( this, tr( "SpatiaLite" ), tr( "Cannot get SpatiaLite select dialog from provider." ) );
|
|
return;
|
|
}
|
|
connect( dbs, SIGNAL( addDatabaseLayers( QStringList const &, QString const & ) ),
|
|
this, SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) );
|
|
dbs->exec();
|
|
delete dbs;
|
|
} // QgisApp::addSpatiaLiteLayer()
|
|
|
|
void QgisApp::addDelimitedTextLayer()
|
|
{
|
|
// show the Delimited text dialog
|
|
QDialog *dts = dynamic_cast<QDialog *>( QgsProviderRegistry::instance()->createSelectionWidget( QStringLiteral( "delimitedtext" ), this ) );
|
|
if ( !dts )
|
|
{
|
|
QMessageBox::warning( this, tr( "Delimited Text" ), tr( "Cannot get Delimited Text select dialog from provider." ) );
|
|
return;
|
|
}
|
|
connect( dts, SIGNAL( addVectorLayer( QString, QString, QString ) ),
|
|
this, SLOT( addSelectedVectorLayer( QString, QString, QString ) ) );
|
|
dts->exec();
|
|
delete dts;
|
|
} // QgisApp::addDelimitedTextLayer()
|
|
|
|
void QgisApp::addVirtualLayer()
|
|
{
|
|
// show the Delimited text dialog
|
|
QDialog *dts = dynamic_cast<QDialog *>( QgsProviderRegistry::instance()->createSelectionWidget( QStringLiteral( "virtual" ), this ) );
|
|
if ( !dts )
|
|
{
|
|
QMessageBox::warning( this, tr( "Virtual layer" ), tr( "Cannot get virtual layer select dialog from provider." ) );
|
|
return;
|
|
}
|
|
connect( dts, SIGNAL( addVectorLayer( QString, QString, QString ) ),
|
|
this, SLOT( addSelectedVectorLayer( QString, QString, QString ) ) );
|
|
connect( dts, SIGNAL( replaceVectorLayer( QString, QString, QString, QString ) ),
|
|
this, SLOT( replaceSelectedVectorLayer( QString, QString, QString, QString ) ) );
|
|
dts->exec();
|
|
delete dts;
|
|
} // QgisApp::addVirtualLayer()
|
|
|
|
void QgisApp::addSelectedVectorLayer( const QString &uri, const QString &layerName, const QString &provider )
|
|
{
|
|
addVectorLayer( uri, layerName, provider );
|
|
} // QgisApp:addSelectedVectorLayer
|
|
|
|
void QgisApp::replaceSelectedVectorLayer( const QString &oldId, const QString &uri, const QString &layerName, const QString &provider )
|
|
{
|
|
QgsMapLayer *old = QgsProject::instance()->mapLayer( oldId );
|
|
if ( !old )
|
|
return;
|
|
QgsVectorLayer *oldLayer = static_cast<QgsVectorLayer *>( old );
|
|
QgsVectorLayer *newLayer = new QgsVectorLayer( uri, layerName, provider );
|
|
if ( !newLayer || !newLayer->isValid() )
|
|
return;
|
|
|
|
QgsProject::instance()->addMapLayer( newLayer, /*addToLegend*/ false, /*takeOwnership*/ true );
|
|
duplicateVectorStyle( oldLayer, newLayer );
|
|
|
|
// insert the new layer just below the old one
|
|
QgsLayerTreeUtils::insertLayerBelow( QgsProject::instance()->layerTreeRoot(), oldLayer, newLayer );
|
|
// and remove the old layer
|
|
QgsProject::instance()->removeMapLayer( oldLayer );
|
|
} // QgisApp:replaceSelectedVectorLayer
|
|
|
|
void QgisApp::addMssqlLayer()
|
|
{
|
|
// show the MSSQL dialog
|
|
QDialog *dbs = dynamic_cast<QDialog *>( QgsProviderRegistry::instance()->createSelectionWidget( QStringLiteral( "mssql" ), this ) );
|
|
if ( !dbs )
|
|
{
|
|
QMessageBox::warning( this, tr( "MSSQL" ), tr( "Cannot get MSSQL select dialog from provider." ) );
|
|
return;
|
|
}
|
|
connect( dbs, SIGNAL( addDatabaseLayers( QStringList const &, QString const & ) ),
|
|
this, SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) );
|
|
dbs->exec();
|
|
delete dbs;
|
|
} // QgisApp::addMssqlLayer()
|
|
|
|
void QgisApp::addDb2Layer()
|
|
{
|
|
// show the DB2 dialog
|
|
QgsDebugMsg( "Show dialog for DB2 " );
|
|
QDialog *dbs = dynamic_cast<QDialog *>( QgsProviderRegistry::instance()->createSelectionWidget( QStringLiteral( "DB2" ), this ) );
|
|
if ( !dbs )
|
|
{
|
|
QMessageBox::warning( this, tr( "DB2" ), tr( "Cannot get DB2 select dialog from provider." ) );
|
|
return;
|
|
}
|
|
connect( dbs, SIGNAL( addDatabaseLayers( QStringList const &, QString const & ) ),
|
|
this, SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) );
|
|
dbs->exec();
|
|
delete dbs;
|
|
} // QgisApp::addDb2Layer()
|
|
|
|
void QgisApp::addOracleLayer()
|
|
{
|
|
#ifdef HAVE_ORACLE
|
|
// show the Oracle dialog
|
|
QDialog *dbs = dynamic_cast<QDialog *>( QgsProviderRegistry::instance()->createSelectionWidget( "oracle", this ) );
|
|
if ( !dbs )
|
|
{
|
|
QMessageBox::warning( this, tr( "Oracle" ), tr( "Cannot get Oracle select dialog from provider." ) );
|
|
return;
|
|
}
|
|
connect( dbs, SIGNAL( addDatabaseLayers( QStringList const &, QString const & ) ),
|
|
this, SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) );
|
|
connect( dbs, SIGNAL( progress( int, int ) ),
|
|
this, SLOT( showProgress( int, int ) ) );
|
|
connect( dbs, SIGNAL( progressMessage( QString ) ),
|
|
this, SLOT( showStatusMessage( QString ) ) );
|
|
dbs->exec();
|
|
delete dbs;
|
|
#endif
|
|
} // QgisApp::addOracleLayer()
|
|
|
|
|
|
void QgisApp::fileExit()
|
|
{
|
|
if ( QgsApplication::taskManager()->countActiveTasks() > 0 )
|
|
{
|
|
QStringList tasks;
|
|
Q_FOREACH ( QgsTask *task, QgsApplication::taskManager()->activeTasks() )
|
|
{
|
|
tasks << tr( " • %1" ).arg( task->description() );
|
|
}
|
|
|
|
// active tasks
|
|
if ( QMessageBox::question( this, tr( "Active tasks" ),
|
|
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( QStringLiteral( "\n" ) ) ),
|
|
QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes )
|
|
{
|
|
QgsApplication::taskManager()->cancelAll();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( saveDirty() )
|
|
{
|
|
closeProject();
|
|
userProfileManager()->setDefaultFromActive();
|
|
qApp->exit( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
void QgisApp::fileNew()
|
|
{
|
|
fileNew( true ); // prompts whether to save project
|
|
} // fileNew()
|
|
|
|
|
|
void QgisApp::fileNewBlank()
|
|
{
|
|
fileNew( true, true );
|
|
}
|
|
|
|
|
|
//as file new but accepts flags to indicate whether we should prompt to save
|
|
void QgisApp::fileNew( bool promptToSaveFlag, bool forceBlank )
|
|
{
|
|
if ( checkTasksDependOnProject() )
|
|
return;
|
|
|
|
if ( promptToSaveFlag )
|
|
{
|
|
if ( !saveDirty() )
|
|
{
|
|
return; //cancel pressed
|
|
}
|
|
}
|
|
|
|
mProjectLastModified = QDateTime();
|
|
|
|
QgsSettings settings;
|
|
|
|
closeProject();
|
|
|
|
QgsProject *prj = QgsProject::instance();
|
|
prj->layerTreeRegistryBridge()->setNewLayersVisible( settings.value( QStringLiteral( "qgis/new_layers_visible" ), true ).toBool() );
|
|
|
|
//set the color for selections
|
|
//the default can be set in qgisoptions
|
|
//use project properties to override the color on a per project basis
|
|
int red = settings.value( QStringLiteral( "qgis/default_selection_color_red" ), 255 ).toInt();
|
|
int green = settings.value( QStringLiteral( "qgis/default_selection_color_green" ), 255 ).toInt();
|
|
int blue = settings.value( QStringLiteral( "qgis/default_selection_color_blue" ), 0 ).toInt();
|
|
int alpha = settings.value( QStringLiteral( "qgis/default_selection_color_alpha" ), 255 ).toInt();
|
|
prj->writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), red );
|
|
prj->writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), green );
|
|
prj->writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), blue );
|
|
prj->writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), alpha );
|
|
|
|
//set the canvas to the default background color
|
|
//the default can be set in qgisoptions
|
|
//use project properties to override the color on a per project basis
|
|
red = settings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
|
|
green = settings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
|
|
blue = settings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
|
|
prj->writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), red );
|
|
prj->writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), green );
|
|
prj->writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), blue );
|
|
|
|
mOverviewCanvas->setBackgroundColor( QColor( red, green, blue ) );
|
|
applyProjectSettingsToCanvas( mMapCanvas );
|
|
|
|
prj->setDirty( false );
|
|
|
|
setTitleBarText_( *this );
|
|
|
|
//QgsDebugMsg("emitting new project signal");
|
|
|
|
// emit signal so listeners know we have a new project
|
|
emit newProject();
|
|
|
|
mMapCanvas->freeze( false );
|
|
mMapCanvas->refresh();
|
|
mMapCanvas->clearExtentHistory();
|
|
mMapCanvas->setRotation( 0.0 );
|
|
mScaleWidget->updateScales();
|
|
|
|
// set project CRS
|
|
QString defCrs = settings.value( QStringLiteral( "Projections/projectDefaultCrs" ), GEO_EPSG_CRS_AUTHID ).toString();
|
|
QgsCoordinateReferenceSystem srs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( defCrs );
|
|
// write the projections _proj string_ to project settings
|
|
prj->setCrs( srs );
|
|
prj->setDirty( false );
|
|
|
|
/* New Empty Project Created
|
|
(before attempting to load custom project templates/filepaths) */
|
|
|
|
// load default template
|
|
/* NOTE: don't open default template on launch until after initialization,
|
|
in case a project was defined via command line */
|
|
|
|
// don't open template if last auto-opening of a project failed
|
|
if ( ! forceBlank )
|
|
{
|
|
forceBlank = ! settings.value( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( true ) ).toBool();
|
|
}
|
|
|
|
if ( ! forceBlank && settings.value( QStringLiteral( "qgis/newProjectDefault" ), QVariant( false ) ).toBool() )
|
|
{
|
|
fileNewFromDefaultTemplate();
|
|
}
|
|
|
|
// set the initial map tool
|
|
mMapCanvas->setMapTool( mMapTools.mPan );
|
|
mNonEditMapTool = mMapTools.mPan; // signals are not yet setup to catch this
|
|
|
|
}
|
|
|
|
bool QgisApp::fileNewFromTemplate( const QString &fileName )
|
|
{
|
|
if ( checkTasksDependOnProject() )
|
|
return false;
|
|
|
|
if ( !saveDirty() )
|
|
{
|
|
return false; //cancel pressed
|
|
}
|
|
|
|
QgsDebugMsg( QString( "loading project template: %1" ).arg( fileName ) );
|
|
if ( addProject( fileName ) )
|
|
{
|
|
// set null filename so we don't override the template
|
|
QgsProject::instance()->setFileName( QString() );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QgisApp::fileNewFromDefaultTemplate()
|
|
{
|
|
QString projectTemplate = QgsApplication::qgisSettingsDirPath() + QStringLiteral( "project_default.qgs" );
|
|
QString msgTxt;
|
|
if ( !projectTemplate.isEmpty() && QFile::exists( projectTemplate ) )
|
|
{
|
|
if ( fileNewFromTemplate( projectTemplate ) )
|
|
{
|
|
return;
|
|
}
|
|
msgTxt = tr( "Default failed to open: %1" );
|
|
}
|
|
else
|
|
{
|
|
msgTxt = tr( "Default not found: %1" );
|
|
}
|
|
messageBar()->pushMessage( tr( "Open Template Project" ),
|
|
msgTxt.arg( projectTemplate ),
|
|
QgsMessageBar::WARNING );
|
|
}
|
|
|
|
void QgisApp::fileOpenAfterLaunch()
|
|
{
|
|
// TODO: move auto-open project options to enums
|
|
|
|
// check if a project is already loaded via command line or filesystem
|
|
if ( !QgsProject::instance()->fileName().isNull() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// check if a data source is already loaded via command line or filesystem
|
|
// empty project with layer loaded, but may not trigger a dirty project at this point
|
|
if ( QgsProject::instance() && QgsProject::instance()->count() > 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// fileNewBlank() has already been called in QgisApp constructor
|
|
// loaded project is either a new blank one, or one from command line/filesystem
|
|
QgsSettings settings;
|
|
QString autoOpenMsgTitle = tr( "Auto-open Project" );
|
|
|
|
// get path of project file to open, or was attempted
|
|
QString projPath;
|
|
|
|
if ( mProjOpen == 0 ) // welcome page
|
|
{
|
|
connect( this, &QgisApp::newProject, this, &QgisApp::showMapCanvas );
|
|
connect( this, &QgisApp::projectRead, this, &QgisApp::showMapCanvas );
|
|
return;
|
|
}
|
|
if ( mProjOpen == 1 && !mRecentProjects.isEmpty() ) // most recent project
|
|
{
|
|
projPath = mRecentProjects.at( 0 ).path;
|
|
}
|
|
if ( mProjOpen == 2 ) // specific project
|
|
{
|
|
projPath = settings.value( QStringLiteral( "qgis/projOpenAtLaunchPath" ) ).toString();
|
|
}
|
|
|
|
// whether last auto-opening of a project failed
|
|
bool projOpenedOK = settings.value( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( true ) ).toBool();
|
|
|
|
// notify user if last attempt at auto-opening a project failed
|
|
|
|
/* NOTE: Notification will not show if last auto-opened project failed but
|
|
next project opened is from command line (minor issue) */
|
|
|
|
/* TODO: Keep projOpenedOKAtLaunch from being reset to true after
|
|
reading command line project (which happens before initialization signal) */
|
|
if ( !projOpenedOK )
|
|
{
|
|
// only show the following 'auto-open project failed' message once, at launch
|
|
settings.setValue( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( true ) );
|
|
|
|
// set auto-open project back to 'New' to avoid re-opening bad project
|
|
settings.setValue( QStringLiteral( "qgis/projOpenAtLaunch" ), QVariant( 0 ) );
|
|
|
|
messageBar()->pushMessage( autoOpenMsgTitle,
|
|
tr( "Failed to open: %1" ).arg( projPath ),
|
|
QgsMessageBar::CRITICAL );
|
|
return;
|
|
}
|
|
|
|
if ( mProjOpen == 3 ) // new project
|
|
{
|
|
// open default template, if defined
|
|
if ( settings.value( QStringLiteral( "qgis/newProjectDefault" ), QVariant( false ) ).toBool() )
|
|
{
|
|
fileNewFromDefaultTemplate();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( projPath.isEmpty() ) // projPath required from here
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !projPath.endsWith( QLatin1String( ".qgs" ), Qt::CaseInsensitive ) )
|
|
{
|
|
messageBar()->pushMessage( autoOpenMsgTitle,
|
|
tr( "Not valid project file: %1" ).arg( projPath ),
|
|
QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
if ( QFile::exists( projPath ) )
|
|
{
|
|
// set flag to check on next app launch if the following project opened OK
|
|
settings.setValue( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( false ) );
|
|
|
|
if ( !addProject( projPath ) )
|
|
{
|
|
messageBar()->pushMessage( autoOpenMsgTitle,
|
|
tr( "Project failed to open: %1" ).arg( projPath ),
|
|
QgsMessageBar::WARNING );
|
|
}
|
|
|
|
if ( projPath.endsWith( QLatin1String( "project_default.qgs" ) ) )
|
|
{
|
|
messageBar()->pushMessage( autoOpenMsgTitle,
|
|
tr( "Default template has been reopened: %1" ).arg( projPath ),
|
|
QgsMessageBar::INFO );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
messageBar()->pushMessage( autoOpenMsgTitle,
|
|
tr( "File not found: %1" ).arg( projPath ),
|
|
QgsMessageBar::WARNING );
|
|
}
|
|
}
|
|
|
|
void QgisApp::fileOpenedOKAfterLaunch()
|
|
{
|
|
QgsSettings settings;
|
|
settings.setValue( QStringLiteral( "qgis/projOpenedOKAtLaunch" ), QVariant( true ) );
|
|
}
|
|
|
|
void QgisApp::fileNewFromTemplateAction( QAction *qAction )
|
|
{
|
|
if ( ! qAction )
|
|
return;
|
|
|
|
if ( qAction->text() == tr( "< Blank >" ) )
|
|
{
|
|
fileNewBlank();
|
|
}
|
|
else
|
|
{
|
|
QgsSettings settings;
|
|
QString templateDirName = settings.value( QStringLiteral( "qgis/projectTemplateDir" ),
|
|
QgsApplication::qgisSettingsDirPath() + "project_templates" ).toString();
|
|
fileNewFromTemplate( templateDirName + QDir::separator() + qAction->text() );
|
|
}
|
|
}
|
|
|
|
|
|
void QgisApp::newVectorLayer()
|
|
{
|
|
QString enc;
|
|
QString fileName = QgsNewVectorLayerDialog::runAndCreateLayer( this, &enc, QgsProject::instance()->defaultCrsForNewLayers() );
|
|
|
|
if ( !fileName.isEmpty() )
|
|
{
|
|
//then add the layer to the view
|
|
QStringList fileNames;
|
|
fileNames.append( fileName );
|
|
//todo: the last parameter will change accordingly to layer type
|
|
addVectorLayers( fileNames, enc, QStringLiteral( "file" ) );
|
|
}
|
|
else if ( fileName.isNull() )
|
|
{
|
|
QLabel *msgLabel = new QLabel( tr( "Layer creation failed. Please check the <a href=\"#messageLog\">message log</a> for further information." ), messageBar() );
|
|
msgLabel->setWordWrap( true );
|
|
connect( msgLabel, &QLabel::linkActivated, mLogDock, &QWidget::show );
|
|
QgsMessageBarItem *item = new QgsMessageBarItem( msgLabel, QgsMessageBar::WARNING );
|
|
messageBar()->pushItem( item );
|
|
}
|
|
}
|
|
|
|
void QgisApp::newMemoryLayer()
|
|
{
|
|
QgsVectorLayer *newLayer = QgsNewMemoryLayerDialog::runAndCreateLayer( this, QgsProject::instance()->defaultCrsForNewLayers() );
|
|
|
|
if ( newLayer )
|
|
{
|
|
//then add the layer to the view
|
|
QList< QgsMapLayer * > layers;
|
|
layers << newLayer;
|
|
|
|
QgsProject::instance()->addMapLayers( layers );
|
|
newLayer->startEditing();
|
|
}
|
|
}
|
|
|
|
void QgisApp::newSpatialiteLayer()
|
|
{
|
|
QgsNewSpatialiteLayerDialog spatialiteDialog( this, QgsGuiUtils::ModalDialogFlags, QgsProject::instance()->defaultCrsForNewLayers() );
|
|
spatialiteDialog.exec();
|
|
}
|
|
|
|
void QgisApp::newGeoPackageLayer()
|
|
{
|
|
QgsNewGeoPackageLayerDialog dialog( this );
|
|
dialog.setCrs( QgsProject::instance()->defaultCrsForNewLayers() );
|
|
dialog.exec();
|
|
}
|
|
|
|
void QgisApp::showRasterCalculator()
|
|
{
|
|
QgsRasterCalcDialog d( this );
|
|
if ( d.exec() == QDialog::Accepted )
|
|
{
|
|
//invoke analysis library
|
|
QgsRasterCalculator rc( d.formulaString(), d.outputFile(), d.outputFormat(), d.outputRectangle(), d.outputCrs(), d.numberOfColumns(), d.numberOfRows(), d.rasterEntries() );
|
|
|
|
QProgressDialog p( tr( "Calculating..." ), tr( "Abort..." ), 0, 0 );
|
|
p.setWindowModality( Qt::WindowModal );
|
|
p.setMaximum( 100.0 );
|
|
QgsFeedback feedback;
|
|
connect( &feedback, &QgsFeedback::progressChanged, &p, &QProgressDialog::setValue );
|
|
connect( &feedback, &QgsFeedback::canceled, &p, &QProgressDialog::cancel );
|
|
QgsRasterCalculator::Result res = static_cast< QgsRasterCalculator::Result >( rc.processCalculation( &feedback ) );
|
|
switch ( res )
|
|
{
|
|
case QgsRasterCalculator::Success:
|
|
if ( d.addLayerToProject() )
|
|
{
|
|
addRasterLayer( d.outputFile(), QFileInfo( d.outputFile() ).baseName() );
|
|
}
|
|
messageBar()->pushMessage( tr( "Raster calculator" ),
|
|
tr( "Calculation complete." ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
break;
|
|
|
|
case QgsRasterCalculator::CreateOutputError:
|
|
messageBar()->pushMessage( tr( "Raster calculator" ),
|
|
tr( "Could not create destination file." ),
|
|
QgsMessageBar::CRITICAL );
|
|
break;
|
|
|
|
case QgsRasterCalculator::InputLayerError:
|
|
messageBar()->pushMessage( tr( "Raster calculator" ),
|
|
tr( "Could not read input layer." ),
|
|
QgsMessageBar::CRITICAL );
|
|
break;
|
|
|
|
case QgsRasterCalculator::Canceled:
|
|
break;
|
|
|
|
case QgsRasterCalculator::ParserError:
|
|
messageBar()->pushMessage( tr( "Raster calculator" ),
|
|
tr( "Could not parse raster formula." ),
|
|
QgsMessageBar::CRITICAL );
|
|
break;
|
|
|
|
case QgsRasterCalculator::MemoryError:
|
|
messageBar()->pushMessage( tr( "Raster calculator" ),
|
|
tr( "Insufficient memory available for operation." ),
|
|
QgsMessageBar::CRITICAL );
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void QgisApp::showAlignRasterTool()
|
|
{
|
|
QgsAlignRasterDialog dlg( this );
|
|
dlg.exec();
|
|
}
|
|
|
|
|
|
void QgisApp::fileOpen()
|
|
{
|
|
if ( checkTasksDependOnProject() )
|
|
return;
|
|
|
|
// possibly save any pending work before opening a new project
|
|
if ( saveDirty() )
|
|
{
|
|
// Retrieve last used project dir from persistent settings
|
|
QgsSettings settings;
|
|
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastProjectDir" ), QDir::homePath() ).toString();
|
|
QString fullPath = QFileDialog::getOpenFileName( this,
|
|
tr( "Choose a QGIS project file to open" ),
|
|
lastUsedDir,
|
|
tr( "QGIS files" ) + " (*.qgs *.qgz *.QGS)" );
|
|
if ( fullPath.isNull() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Fix by Tim - getting the dirPath from the dialog
|
|
// directly truncates the last node in the dir path.
|
|
// This is a workaround for that
|
|
QFileInfo myFI( fullPath );
|
|
QString myPath = myFI.path();
|
|
// Persist last used project dir
|
|
settings.setValue( QStringLiteral( "UI/lastProjectDir" ), myPath );
|
|
|
|
// open the selected project
|
|
addProject( fullPath );
|
|
}
|
|
} // QgisApp::fileOpen
|
|
|
|
void QgisApp::enableProjectMacros()
|
|
{
|
|
mTrustedMacros = true;
|
|
|
|
// load macros
|
|
QgsPythonRunner::run( QStringLiteral( "qgis.utils.reloadProjectMacros()" ) );
|
|
}
|
|
|
|
/**
|
|
adds a saved project to qgis, usually called on startup by specifying a
|
|
project file on the command line
|
|
*/
|
|
bool QgisApp::addProject( const QString &projectFile )
|
|
{
|
|
// close the previous opened project if any
|
|
closeProject();
|
|
|
|
QFileInfo pfi( projectFile );
|
|
mStatusBar->showMessage( tr( "Loading project: %1" ).arg( pfi.fileName() ) );
|
|
qApp->processEvents();
|
|
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
|
|
bool autoSetupOnFirstLayer = mLayerTreeCanvasBridge->autoSetupOnFirstLayer();
|
|
mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( false );
|
|
|
|
if ( !QgsProject::instance()->read( projectFile ) && !QgsZipUtils::isZipFile( projectFile ) )
|
|
{
|
|
QString backupFile = projectFile + "~";
|
|
QString loadBackupPrompt;
|
|
QMessageBox::StandardButtons buttons;
|
|
if ( QFile( backupFile ).exists() )
|
|
{
|
|
loadBackupPrompt = "\n\n" + tr( "Do you want to open the backup file\n%1\ninstead?" ).arg( backupFile );
|
|
buttons |= QMessageBox::Yes;
|
|
buttons |= QMessageBox::No;
|
|
}
|
|
else
|
|
{
|
|
buttons |= QMessageBox::Ok;
|
|
}
|
|
QApplication::restoreOverrideCursor();
|
|
mStatusBar->clearMessage();
|
|
|
|
int r = QMessageBox::critical( this,
|
|
tr( "Unable to open project" ),
|
|
QgsProject::instance()->error() + loadBackupPrompt,
|
|
buttons );
|
|
|
|
if ( QMessageBox::Yes == r && addProject( backupFile ) )
|
|
{
|
|
// We loaded data from the backup file, but we pretend to work on the original project file.
|
|
QgsProject::instance()->setFileName( projectFile );
|
|
QgsProject::instance()->setDirty( true );
|
|
mProjectLastModified = pfi.lastModified();
|
|
return true;
|
|
}
|
|
|
|
mMapCanvas->freeze( false );
|
|
mMapCanvas->refresh();
|
|
return false;
|
|
}
|
|
|
|
mProjectLastModified = pfi.lastModified();
|
|
|
|
setTitleBarText_( *this );
|
|
int myRedInt = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), 255 );
|
|
int myGreenInt = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), 255 );
|
|
int myBlueInt = QgsProject::instance()->readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), 255 );
|
|
QColor myColor = QColor( myRedInt, myGreenInt, myBlueInt );
|
|
mOverviewCanvas->setBackgroundColor( myColor );
|
|
|
|
applyProjectSettingsToCanvas( mMapCanvas );
|
|
|
|
//load project scales
|
|
bool projectScales = QgsProject::instance()->readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) );
|
|
if ( projectScales )
|
|
{
|
|
mScaleWidget->updateScales( QgsProject::instance()->readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) ) );
|
|
}
|
|
|
|
mMapCanvas->updateScale();
|
|
QgsDebugMsg( "Scale restored..." );
|
|
|
|
mActionFilterLegend->setChecked( QgsProject::instance()->readBoolEntry( QStringLiteral( "Legend" ), QStringLiteral( "filterByMap" ) ) );
|
|
|
|
QgsSettings settings;
|
|
|
|
#ifdef WITH_BINDINGS
|
|
// does the project have any macros?
|
|
if ( mPythonUtils && mPythonUtils->isEnabled() )
|
|
{
|
|
if ( !QgsProject::instance()->readEntry( QStringLiteral( "Macros" ), QStringLiteral( "/pythonCode" ), QString() ).isEmpty() )
|
|
{
|
|
int enableMacros = settings.value( QStringLiteral( "qgis/enableMacros" ), 1 ).toInt();
|
|
// 0 = never, 1 = ask, 2 = just for this session, 3 = always
|
|
|
|
if ( enableMacros == 3 || enableMacros == 2 )
|
|
{
|
|
enableProjectMacros();
|
|
}
|
|
else if ( enableMacros == 1 ) // ask
|
|
{
|
|
// create the notification widget for macros
|
|
|
|
|
|
QToolButton *btnEnableMacros = new QToolButton();
|
|
btnEnableMacros->setText( tr( "Enable macros" ) );
|
|
btnEnableMacros->setStyleSheet( QStringLiteral( "background-color: rgba(255, 255, 255, 0); color: black; text-decoration: underline;" ) );
|
|
btnEnableMacros->setCursor( Qt::PointingHandCursor );
|
|
btnEnableMacros->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
|
|
|
|
QgsMessageBarItem *macroMsg = new QgsMessageBarItem(
|
|
tr( "Security warning" ),
|
|
tr( "project macros have been disabled." ),
|
|
btnEnableMacros,
|
|
QgsMessageBar::WARNING,
|
|
0,
|
|
mInfoBar );
|
|
|
|
connect( btnEnableMacros, &QToolButton::clicked, this, [this, macroMsg]
|
|
{
|
|
enableProjectMacros();
|
|
mInfoBar->popWidget( macroMsg );
|
|
} );
|
|
|
|
// display the macros notification widget
|
|
mInfoBar->pushItem( macroMsg );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
emit projectRead(); // let plug-ins know that we've read in a new
|
|
// project so that they can check any project
|
|
// specific plug-in state
|
|
|
|
// add this to the list of recently used project files
|
|
saveRecentProjectPath( projectFile, false );
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
if ( autoSetupOnFirstLayer )
|
|
mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( true );
|
|
|
|
mMapCanvas->freeze( false );
|
|
mMapCanvas->refresh();
|
|
|
|
mStatusBar->showMessage( tr( "Project loaded" ), 3000 );
|
|
return true;
|
|
} // QgisApp::addProject(QString projectFile)
|
|
|
|
|
|
|
|
bool QgisApp::fileSave()
|
|
{
|
|
// if we don't have a file name, then obviously we need to get one; note
|
|
// that the project file name is reset to null in fileNew()
|
|
QFileInfo fullPath;
|
|
|
|
if ( QgsProject::instance()->fileName().isNull() )
|
|
{
|
|
// Retrieve last used project dir from persistent settings
|
|
QgsSettings settings;
|
|
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastProjectDir" ), QDir::homePath() ).toString();
|
|
|
|
const QString qgsExt = tr( "QGIS files" ) + " (*.qgs)";
|
|
const QString zipExt = tr( "QGZ files" ) + " (*.qgz)";
|
|
QString filter;
|
|
QString path = QFileDialog::getSaveFileName(
|
|
this,
|
|
tr( "Choose a QGIS project file" ),
|
|
lastUsedDir + '/' + QgsProject::instance()->title(),
|
|
qgsExt + ";;" + zipExt, &filter );
|
|
if ( path.isEmpty() )
|
|
return false;
|
|
|
|
fullPath.setFile( path );
|
|
|
|
// make sure we have the .qgs extension in the file name
|
|
if ( filter == zipExt )
|
|
{
|
|
if ( fullPath.suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) != 0 )
|
|
fullPath.setFile( fullPath.filePath() + ".qgz" );
|
|
}
|
|
else
|
|
{
|
|
if ( fullPath.suffix().compare( QLatin1String( "qgs" ), Qt::CaseInsensitive ) != 0 )
|
|
fullPath.setFile( fullPath.filePath() + ".qgs" );
|
|
}
|
|
|
|
QgsProject::instance()->setFileName( fullPath.filePath() );
|
|
}
|
|
else
|
|
{
|
|
QFileInfo fi( QgsProject::instance()->fileName() );
|
|
fullPath = fi.absoluteFilePath();
|
|
if ( fi.exists() && !mProjectLastModified.isNull() && mProjectLastModified != fi.lastModified() )
|
|
{
|
|
if ( QMessageBox::warning( this,
|
|
tr( "Project file was changed" ),
|
|
tr( "The loaded project file on disk was meanwhile changed. Do you want to overwrite the changes?\n"
|
|
"\nLast modification date on load was: %1"
|
|
"\nCurrent last modification date is: %2" )
|
|
.arg( mProjectLastModified.toString( Qt::DefaultLocaleLongDate ),
|
|
fi.lastModified().toString( Qt::DefaultLocaleLongDate ) ),
|
|
QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel )
|
|
return false;
|
|
}
|
|
|
|
if ( fi.exists() && ! fi.isWritable() )
|
|
{
|
|
messageBar()->pushMessage( tr( "Insufficient permissions" ),
|
|
tr( "The project file is not writable." ),
|
|
QgsMessageBar::WARNING );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( QgsProject::instance()->write() )
|
|
{
|
|
setTitleBarText_( *this ); // update title bar
|
|
mStatusBar->showMessage( tr( "Saved project to: %1" ).arg( QDir::toNativeSeparators( QgsProject::instance()->fileName() ) ), 5000 );
|
|
|
|
saveRecentProjectPath( fullPath.filePath() );
|
|
|
|
QFileInfo fi( QgsProject::instance()->fileName() );
|
|
mProjectLastModified = fi.lastModified();
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::critical( this,
|
|
tr( "Unable to save project %1" ).arg( QDir::toNativeSeparators( QgsProject::instance()->fileName() ) ),
|
|
QgsProject::instance()->error() );
|
|
return false;
|
|
}
|
|
|
|
// run the saved project macro
|
|
if ( mTrustedMacros )
|
|
{
|
|
QgsPythonRunner::run( QStringLiteral( "qgis.utils.saveProjectMacro();" ) );
|
|
}
|
|
|
|
return true;
|
|
} // QgisApp::fileSave
|
|
|
|
void QgisApp::fileSaveAs()
|
|
{
|
|
// Retrieve last used project dir from persistent settings
|
|
QgsSettings settings;
|
|
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastProjectDir" ), QDir::homePath() ).toString();
|
|
|
|
const QString qgsExt = tr( "QGIS files" ) + " (*.qgs *.QGS)";
|
|
const QString zipExt = tr( "QGZ files" ) + " (*.qgz)";
|
|
QString filter;
|
|
QString path = QFileDialog::getSaveFileName( this,
|
|
tr( "Choose a file name to save the QGIS project file as" ),
|
|
lastUsedDir + '/' + QgsProject::instance()->title(),
|
|
qgsExt + ";;" + zipExt, &filter );
|
|
if ( path.isEmpty() )
|
|
return;
|
|
|
|
QFileInfo fullPath( path );
|
|
|
|
settings.setValue( QStringLiteral( "UI/lastProjectDir" ), fullPath.path() );
|
|
|
|
if ( filter == zipExt )
|
|
{
|
|
if ( fullPath.suffix().compare( QLatin1String( "qgz" ), Qt::CaseInsensitive ) != 0 )
|
|
fullPath.setFile( fullPath.filePath() + ".qgz" );
|
|
}
|
|
else // .qgs
|
|
{
|
|
if ( fullPath.suffix().compare( QLatin1String( "qgs" ), Qt::CaseInsensitive ) != 0 )
|
|
fullPath.setFile( fullPath.filePath() + ".qgs" );
|
|
}
|
|
|
|
QgsProject::instance()->setFileName( fullPath.filePath() );
|
|
|
|
if ( QgsProject::instance()->write() )
|
|
{
|
|
setTitleBarText_( *this ); // update title bar
|
|
mStatusBar->showMessage( tr( "Saved project to: %1" ).arg( QDir::toNativeSeparators( QgsProject::instance()->fileName() ) ), 5000 );
|
|
// add this to the list of recently used project files
|
|
saveRecentProjectPath( fullPath.filePath() );
|
|
mProjectLastModified = fullPath.lastModified();
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::critical( this,
|
|
tr( "Unable to save project %1" ).arg( QDir::toNativeSeparators( QgsProject::instance()->fileName() ) ),
|
|
QgsProject::instance()->error(),
|
|
QMessageBox::Ok,
|
|
Qt::NoButton );
|
|
}
|
|
} // QgisApp::fileSaveAs
|
|
|
|
void QgisApp::dxfExport()
|
|
{
|
|
QgsDxfExportDialog d;
|
|
if ( d.exec() == QDialog::Accepted )
|
|
{
|
|
QgsDxfExport dxfExport;
|
|
|
|
QgsMapSettings settings( mapCanvas()->mapSettings() );
|
|
settings.setLayerStyleOverrides( QgsProject::instance()->mapThemeCollection()->mapThemeStyleOverrides( d.mapTheme() ) );
|
|
dxfExport.setMapSettings( settings );
|
|
dxfExport.addLayers( d.layers() );
|
|
dxfExport.setSymbologyScale( d.symbologyScale() );
|
|
dxfExport.setSymbologyExport( d.symbologyMode() );
|
|
dxfExport.setLayerTitleAsName( d.layerTitleAsName() );
|
|
dxfExport.setDestinationCrs( d.crs() );
|
|
dxfExport.setForce2d( d.force2d() );
|
|
if ( mapCanvas() )
|
|
{
|
|
//extent
|
|
if ( d.exportMapExtent() )
|
|
{
|
|
QgsCoordinateTransform t( mapCanvas()->mapSettings().destinationCrs(), d.crs() );
|
|
dxfExport.setExtent( t.transformBoundingBox( mapCanvas()->extent() ) );
|
|
}
|
|
}
|
|
|
|
QString fileName( d.saveFile() );
|
|
if ( !fileName.endsWith( QLatin1String( ".dxf" ), Qt::CaseInsensitive ) )
|
|
fileName += QLatin1String( ".dxf" );
|
|
QFile dxfFile( fileName );
|
|
QApplication::setOverrideCursor( Qt::BusyCursor );
|
|
if ( dxfExport.writeToFile( &dxfFile, d.encoding() ) == 0 )
|
|
{
|
|
messageBar()->pushMessage( tr( "DXF export completed" ), QgsMessageBar::INFO, 4 );
|
|
}
|
|
else
|
|
{
|
|
messageBar()->pushMessage( tr( "DXF export failed" ), QgsMessageBar::CRITICAL, 4 );
|
|
}
|
|
QApplication::restoreOverrideCursor();
|
|
}
|
|
}
|
|
|
|
void QgisApp::dwgImport()
|
|
{
|
|
QgsDwgImportDialog d;
|
|
d.exec();
|
|
}
|
|
|
|
void QgisApp::openLayerDefinition( const QString &path )
|
|
{
|
|
QString errorMessage;
|
|
bool loaded = QgsLayerDefinition::loadLayerDefinition( path, QgsProject::instance(), QgsProject::instance()->layerTreeRoot(), errorMessage );
|
|
if ( !loaded )
|
|
{
|
|
QgsDebugMsg( errorMessage );
|
|
messageBar()->pushMessage( tr( "Error loading layer definition" ), errorMessage, QgsMessageBar::WARNING );
|
|
}
|
|
}
|
|
|
|
void QgisApp::openTemplate( const QString &fileName )
|
|
{
|
|
QFile templateFile;
|
|
templateFile.setFileName( fileName );
|
|
|
|
if ( !templateFile.open( QIODevice::ReadOnly ) )
|
|
{
|
|
messageBar()->pushMessage( tr( "Load template" ), tr( "Could not read template file" ), QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
QString title;
|
|
if ( !uniqueComposerTitle( this, title, true ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsComposer *newComposer = createNewComposer( title );
|
|
if ( !newComposer )
|
|
{
|
|
messageBar()->pushMessage( tr( "Load template" ), tr( "Could not create composer" ), QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
bool loadedOk = false;
|
|
QDomDocument templateDoc;
|
|
if ( templateDoc.setContent( &templateFile, false ) )
|
|
{
|
|
loadedOk = newComposer->loadFromTemplate( templateDoc, true );
|
|
newComposer->activate();
|
|
}
|
|
|
|
if ( !loadedOk )
|
|
{
|
|
newComposer->close();
|
|
deleteComposer( newComposer );
|
|
newComposer = nullptr;
|
|
messageBar()->pushMessage( tr( "Load template" ), tr( "Could not load template file" ), QgsMessageBar::WARNING );
|
|
}
|
|
}
|
|
|
|
// Open the project file corresponding to the
|
|
// path at the given index in mRecentProjectPaths
|
|
void QgisApp::openProject( QAction *action )
|
|
{
|
|
// possibly save any pending work before opening a different project
|
|
Q_ASSERT( action );
|
|
|
|
if ( checkTasksDependOnProject() )
|
|
return;
|
|
|
|
QString debugme = action->data().toString();
|
|
if ( saveDirty() )
|
|
addProject( debugme );
|
|
}
|
|
|
|
void QgisApp::runScript( const QString &filePath )
|
|
{
|
|
#ifdef WITH_BINDINGS
|
|
if ( !mPythonUtils || !mPythonUtils->isEnabled() )
|
|
return;
|
|
|
|
mPythonUtils->runString(
|
|
QString( "import sys\n"
|
|
"from qgis.utils import iface\n"
|
|
"exec(open(\"%1\".replace(\"\\\\\", \"/\").encode(sys.getfilesystemencoding())).read())\n" ).arg( filePath )
|
|
, tr( "Failed to run Python script:" ), false );
|
|
#else
|
|
Q_UNUSED( filePath );
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
Open the specified project file; prompt to save previous project if necessary.
|
|
Used to process a commandline argument or OpenDocument AppleEvent.
|
|
*/
|
|
void QgisApp::openProject( const QString &fileName )
|
|
{
|
|
if ( checkTasksDependOnProject() )
|
|
return;
|
|
|
|
// possibly save any pending work before opening a different project
|
|
if ( saveDirty() )
|
|
{
|
|
// error handling and reporting is in addProject() function
|
|
addProject( fileName );
|
|
}
|
|
}
|
|
|
|
/**
|
|
Open a raster or vector file; ignore other files.
|
|
Used to process a commandline argument or OpenDocument AppleEvent.
|
|
@returns true if the file is successfully opened
|
|
*/
|
|
bool QgisApp::openLayer( const QString &fileName, bool allowInteractive )
|
|
{
|
|
QFileInfo fileInfo( fileName );
|
|
bool ok( false );
|
|
|
|
CPLPushErrorHandler( CPLQuietErrorHandler );
|
|
|
|
// if needed prompt for zipitem layers
|
|
QString vsiPrefix = QgsZipItem::vsiPrefix( fileName );
|
|
if ( vsiPrefix == QLatin1String( "/vsizip/" ) || vsiPrefix == QLatin1String( "/vsitar/" ) )
|
|
{
|
|
if ( askUserForZipItemLayers( fileName ) )
|
|
{
|
|
CPLPopErrorHandler();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// try to load it as raster
|
|
if ( QgsRasterLayer::isValidRasterFileName( fileName ) )
|
|
{
|
|
// open .adf as a directory
|
|
if ( fileName.endsWith( QLatin1String( ".adf" ), Qt::CaseInsensitive ) )
|
|
{
|
|
QString dirName = fileInfo.path();
|
|
ok = addRasterLayer( dirName, QFileInfo( dirName ).completeBaseName() );
|
|
}
|
|
else
|
|
ok = addRasterLayer( fileName, fileInfo.completeBaseName() );
|
|
}
|
|
// TODO - should we really call isValidRasterFileName() before addRasterLayer()
|
|
// this results in 2 calls to GDALOpen()
|
|
// I think (Radim) that it is better to test only first if valid,
|
|
// addRasterLayer() is really trying to add layer and gives error if fails
|
|
//
|
|
// if ( addRasterLayer( fileName, fileInfo.completeBaseName() ) )
|
|
// {
|
|
// ok = true );
|
|
// }
|
|
else // nope - try to load it as a shape/ogr
|
|
{
|
|
if ( allowInteractive )
|
|
{
|
|
ok = addVectorLayers( QStringList( fileName ), QStringLiteral( "System" ), QStringLiteral( "file" ) );
|
|
}
|
|
else
|
|
{
|
|
ok = addVectorLayer( fileName, fileInfo.completeBaseName(), QStringLiteral( "ogr" ) );
|
|
}
|
|
}
|
|
|
|
CPLPopErrorHandler();
|
|
|
|
if ( !ok )
|
|
{
|
|
// we have no idea what this file is...
|
|
QgsMessageLog::logMessage( tr( "Unable to load %1" ).arg( fileName ) );
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
// Open a file specified by a commandline argument, Drop or FileOpen event.
|
|
void QgisApp::openFile( const QString &fileName )
|
|
{
|
|
// check to see if we are opening a project file
|
|
QFileInfo fi( fileName );
|
|
if ( fi.completeSuffix() == QLatin1String( "qgs" ) )
|
|
{
|
|
QgsDebugMsg( "Opening project " + fileName );
|
|
openProject( fileName );
|
|
}
|
|
else if ( fi.completeSuffix() == QLatin1String( "qlr" ) )
|
|
{
|
|
openLayerDefinition( fileName );
|
|
}
|
|
else if ( fi.completeSuffix() == QLatin1String( "qpt" ) )
|
|
{
|
|
openTemplate( fileName );
|
|
}
|
|
else if ( fi.completeSuffix() == QLatin1String( "py" ) )
|
|
{
|
|
runScript( fileName );
|
|
}
|
|
else
|
|
{
|
|
QgsDebugMsg( "Adding " + fileName + " to the map canvas" );
|
|
openLayer( fileName, true );
|
|
}
|
|
}
|
|
|
|
|
|
void QgisApp::newPrintComposer()
|
|
{
|
|
QString title;
|
|
if ( !uniqueComposerTitle( this, title, true ) )
|
|
{
|
|
return;
|
|
}
|
|
createNewComposer( title );
|
|
}
|
|
|
|
void QgisApp::showComposerManager()
|
|
{
|
|
if ( !mComposerManager )
|
|
{
|
|
mComposerManager = new QgsComposerManager( nullptr, Qt::Window );
|
|
connect( mComposerManager, &QDialog::finished, this, &QgisApp::deleteComposerManager );
|
|
}
|
|
mComposerManager->show();
|
|
mComposerManager->activate();
|
|
}
|
|
|
|
void QgisApp::deleteComposerManager()
|
|
{
|
|
mComposerManager->deleteLater();
|
|
mComposerManager = nullptr;
|
|
}
|
|
|
|
void QgisApp::disablePreviewMode()
|
|
{
|
|
mMapCanvas->setPreviewModeEnabled( false );
|
|
}
|
|
|
|
void QgisApp::activateGrayscalePreview()
|
|
{
|
|
mMapCanvas->setPreviewModeEnabled( true );
|
|
mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewGrayscale );
|
|
}
|
|
|
|
void QgisApp::activateMonoPreview()
|
|
{
|
|
mMapCanvas->setPreviewModeEnabled( true );
|
|
mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewMono );
|
|
}
|
|
|
|
void QgisApp::activateProtanopePreview()
|
|
{
|
|
mMapCanvas->setPreviewModeEnabled( true );
|
|
mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewProtanope );
|
|
}
|
|
|
|
void QgisApp::activateDeuteranopePreview()
|
|
{
|
|
mMapCanvas->setPreviewModeEnabled( true );
|
|
mMapCanvas->setPreviewMode( QgsPreviewEffect::PreviewDeuteranope );
|
|
}
|
|
|
|
void QgisApp::toggleFilterLegendByExpression( bool checked )
|
|
{
|
|
QgsLayerTreeNode *node = mLayerTreeView->currentNode();
|
|
if ( ! node )
|
|
return;
|
|
|
|
if ( QgsLayerTree::isLayer( node ) )
|
|
{
|
|
QString e = mLegendExpressionFilterButton->expressionText();
|
|
QgsLayerTreeUtils::setLegendFilterByExpression( *QgsLayerTree::toLayer( node ), e, checked );
|
|
}
|
|
|
|
updateFilterLegend();
|
|
}
|
|
|
|
void QgisApp::updateFilterLegend()
|
|
{
|
|
bool hasExpressions = mLegendExpressionFilterButton->isChecked() && QgsLayerTreeUtils::hasLegendFilterExpression( *mLayerTreeView->layerTreeModel()->rootGroup() );
|
|
if ( mActionFilterLegend->isChecked() || hasExpressions )
|
|
{
|
|
layerTreeView()->layerTreeModel()->setLegendFilter( &mMapCanvas->mapSettings(),
|
|
/* useExtent */ mActionFilterLegend->isChecked(),
|
|
/* polygon */ QgsGeometry(),
|
|
hasExpressions );
|
|
}
|
|
else
|
|
{
|
|
layerTreeView()->layerTreeModel()->setLegendFilterByMap( nullptr );
|
|
}
|
|
}
|
|
|
|
void QgisApp::saveMapAsImage()
|
|
{
|
|
QList< QgsDecorationItem * > decorations;
|
|
Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems )
|
|
{
|
|
if ( decoration->enabled() )
|
|
{
|
|
decorations << decoration;
|
|
}
|
|
}
|
|
|
|
QgsMapSaveDialog *dlg = new QgsMapSaveDialog( this, mMapCanvas, decorations, QgsProject::instance()->annotationManager()->annotations() );
|
|
dlg->setAttribute( Qt::WA_DeleteOnClose );
|
|
dlg->show();
|
|
} // saveMapAsImage
|
|
|
|
void QgisApp::saveMapAsPdf()
|
|
{
|
|
QList< QgsDecorationItem * > decorations;
|
|
Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems )
|
|
{
|
|
if ( decoration->enabled() )
|
|
{
|
|
decorations << decoration;
|
|
}
|
|
}
|
|
|
|
QgsMapSaveDialog *dlg = new QgsMapSaveDialog( this, mMapCanvas, decorations, QgsProject::instance()->annotationManager()->annotations(), QgsMapSaveDialog::Pdf );
|
|
dlg->setAttribute( Qt::WA_DeleteOnClose );
|
|
dlg->show();
|
|
} // saveMapAsPdf
|
|
|
|
//overloaded version of the above function
|
|
void QgisApp::saveMapAsImage( const QString &imageFileNameQString, QPixmap *theQPixmap )
|
|
{
|
|
if ( imageFileNameQString.isEmpty() )
|
|
{
|
|
//no fileName chosen
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//force the size of the canvas
|
|
mMapCanvas->resize( theQPixmap->width(), theQPixmap->height() );
|
|
//save the mapview to the selected file
|
|
mMapCanvas->saveAsImage( imageFileNameQString, theQPixmap );
|
|
}
|
|
} // saveMapAsImage
|
|
|
|
//reimplements method from base (gui) class
|
|
void QgisApp::addAllToOverview()
|
|
{
|
|
if ( mLayerTreeView )
|
|
{
|
|
Q_FOREACH ( QgsLayerTreeLayer *nodeL, mLayerTreeView->layerTreeModel()->rootGroup()->findLayers() )
|
|
nodeL->setCustomProperty( QStringLiteral( "overview" ), 1 );
|
|
}
|
|
|
|
markDirty();
|
|
}
|
|
|
|
//reimplements method from base (gui) class
|
|
void QgisApp::removeAllFromOverview()
|
|
{
|
|
if ( mLayerTreeView )
|
|
{
|
|
Q_FOREACH ( QgsLayerTreeLayer *nodeL, mLayerTreeView->layerTreeModel()->rootGroup()->findLayers() )
|
|
nodeL->setCustomProperty( QStringLiteral( "overview" ), 0 );
|
|
}
|
|
|
|
markDirty();
|
|
}
|
|
|
|
void QgisApp::toggleFullScreen()
|
|
{
|
|
if ( mFullScreenMode )
|
|
{
|
|
if ( mPrevScreenModeMaximized )
|
|
{
|
|
// Change to maximized state. Just calling showMaximized() results in
|
|
// the window going to the normal state. Calling showNormal() then
|
|
// showMaxmized() is a work-around. Turn off rendering for this as it
|
|
// would otherwise cause two re-renders of the map, which can take a
|
|
// long time.
|
|
bool wasFrozen = mapCanvas()->isFrozen();
|
|
freezeCanvases();
|
|
showNormal();
|
|
showMaximized();
|
|
if ( !wasFrozen )
|
|
freezeCanvases( false );
|
|
mPrevScreenModeMaximized = false;
|
|
}
|
|
else
|
|
{
|
|
showNormal();
|
|
}
|
|
mFullScreenMode = false;
|
|
}
|
|
else
|
|
{
|
|
if ( isMaximized() )
|
|
{
|
|
mPrevScreenModeMaximized = true;
|
|
}
|
|
showFullScreen();
|
|
mFullScreenMode = true;
|
|
}
|
|
}
|
|
|
|
void QgisApp::togglePanelsVisibility()
|
|
{
|
|
QgsSettings settings;
|
|
|
|
QStringList docksTitle = settings.value( QStringLiteral( "UI/hiddenDocksTitle" ), QString() ).toStringList();
|
|
QStringList docksActive = settings.value( QStringLiteral( "UI/hiddenDocksActive" ), QString() ).toStringList();
|
|
|
|
QList<QDockWidget *> docks = findChildren<QDockWidget *>();
|
|
QList<QTabBar *> tabBars = findChildren<QTabBar *>();
|
|
|
|
if ( docksTitle.isEmpty() )
|
|
{
|
|
|
|
Q_FOREACH ( QDockWidget *dock, docks )
|
|
{
|
|
if ( dock->isVisible() && !dock->isFloating() )
|
|
{
|
|
docksTitle << dock->windowTitle();
|
|
dock->setVisible( false );
|
|
}
|
|
}
|
|
|
|
Q_FOREACH ( QTabBar *tabBar, tabBars )
|
|
{
|
|
docksActive << tabBar->tabText( tabBar->currentIndex() );
|
|
}
|
|
|
|
settings.setValue( QStringLiteral( "UI/hiddenDocksTitle" ), docksTitle );
|
|
settings.setValue( QStringLiteral( "UI/hiddenDocksActive" ), docksActive );
|
|
}
|
|
else
|
|
{
|
|
Q_FOREACH ( QDockWidget *dock, docks )
|
|
{
|
|
if ( docksTitle.contains( dock->windowTitle() ) )
|
|
{
|
|
dock->setVisible( true );
|
|
}
|
|
}
|
|
|
|
Q_FOREACH ( QTabBar *tabBar, tabBars )
|
|
{
|
|
for ( int i = 0; i < tabBar->count(); ++i )
|
|
{
|
|
if ( docksActive.contains( tabBar->tabText( i ) ) )
|
|
{
|
|
tabBar->setCurrentIndex( i );
|
|
}
|
|
}
|
|
}
|
|
|
|
settings.setValue( QStringLiteral( "UI/hiddenDocksTitle" ), QStringList() );
|
|
settings.setValue( QStringLiteral( "UI/hiddenDocksActive" ), QStringList() );
|
|
}
|
|
}
|
|
|
|
void QgisApp::showActiveWindowMinimized()
|
|
{
|
|
QWidget *window = QApplication::activeWindow();
|
|
if ( window )
|
|
{
|
|
window->showMinimized();
|
|
}
|
|
}
|
|
|
|
void QgisApp::toggleActiveWindowMaximized()
|
|
{
|
|
QWidget *window = QApplication::activeWindow();
|
|
if ( window )
|
|
{
|
|
if ( window->isMaximized() )
|
|
window->showNormal();
|
|
else
|
|
window->showMaximized();
|
|
}
|
|
}
|
|
|
|
void QgisApp::activate()
|
|
{
|
|
raise();
|
|
setWindowState( windowState() & ~Qt::WindowMinimized );
|
|
activateWindow();
|
|
}
|
|
|
|
void QgisApp::bringAllToFront()
|
|
{
|
|
QgsGui::nativePlatformInterface()->currentAppActivateIgnoringOtherApps();
|
|
}
|
|
|
|
void QgisApp::addWindow( QAction *action )
|
|
{
|
|
#ifdef Q_OS_MAC
|
|
mWindowActions->addAction( action );
|
|
mWindowMenu->addAction( action );
|
|
action->setCheckable( true );
|
|
action->setChecked( true );
|
|
#else
|
|
Q_UNUSED( action );
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::removeWindow( QAction *action )
|
|
{
|
|
#ifdef Q_OS_MAC
|
|
mWindowActions->removeAction( action );
|
|
mWindowMenu->removeAction( action );
|
|
#else
|
|
Q_UNUSED( action );
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::stopRendering()
|
|
{
|
|
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
|
|
canvas->stopRendering();
|
|
}
|
|
|
|
//reimplements method from base (gui) class
|
|
void QgisApp::hideAllLayers()
|
|
{
|
|
QgsDebugMsg( "hiding all layers!" );
|
|
mLayerTreeView->layerTreeModel()->rootGroup()->setItemVisibilityCheckedRecursive( false );
|
|
|
|
}
|
|
|
|
|
|
// reimplements method from base (gui) class
|
|
void QgisApp::showAllLayers()
|
|
{
|
|
QgsDebugMsg( "Showing all layers!" );
|
|
mLayerTreeView->layerTreeModel()->rootGroup()->setItemVisibilityCheckedRecursive( true );
|
|
}
|
|
|
|
//reimplements method from base (gui) class
|
|
void QgisApp::hideSelectedLayers()
|
|
{
|
|
QgsDebugMsg( "hiding selected layers!" );
|
|
|
|
Q_FOREACH ( QgsLayerTreeNode *node, mLayerTreeView->selectedNodes() )
|
|
{
|
|
node->setItemVisibilityChecked( false );
|
|
}
|
|
}
|
|
|
|
void QgisApp::hideDeselectedLayers()
|
|
{
|
|
QList<QgsLayerTreeLayer *> selectedLayerNodes = mLayerTreeView->selectedLayerNodes();
|
|
|
|
Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, mLayerTreeView->layerTreeModel()->rootGroup()->findLayers() )
|
|
{
|
|
if ( selectedLayerNodes.contains( nodeLayer ) )
|
|
continue;
|
|
nodeLayer->setItemVisibilityChecked( false );
|
|
}
|
|
}
|
|
|
|
// reimplements method from base (gui) class
|
|
void QgisApp::showSelectedLayers()
|
|
{
|
|
QgsDebugMsg( "show selected layers!" );
|
|
|
|
Q_FOREACH ( QgsLayerTreeNode *node, mLayerTreeView->selectedNodes() )
|
|
{
|
|
QgsLayerTreeNode *nodeIter = node;
|
|
while ( nodeIter )
|
|
{
|
|
nodeIter->setItemVisibilityChecked( true );
|
|
nodeIter = nodeIter->parent();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void QgisApp::zoomIn()
|
|
{
|
|
QgsDebugMsg( "Setting map tool to zoomIn" );
|
|
|
|
mMapCanvas->setMapTool( mMapTools.mZoomIn );
|
|
}
|
|
|
|
|
|
void QgisApp::zoomOut()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mZoomOut );
|
|
}
|
|
|
|
void QgisApp::zoomToSelected()
|
|
{
|
|
mMapCanvas->zoomToSelected();
|
|
}
|
|
|
|
void QgisApp::panToSelected()
|
|
{
|
|
mMapCanvas->panToSelected();
|
|
}
|
|
|
|
void QgisApp::pan()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mPan );
|
|
}
|
|
|
|
void QgisApp::zoomFull()
|
|
{
|
|
mMapCanvas->zoomToFullExtent();
|
|
}
|
|
|
|
void QgisApp::zoomToPrevious()
|
|
{
|
|
mMapCanvas->zoomToPreviousExtent();
|
|
}
|
|
|
|
void QgisApp::zoomToNext()
|
|
{
|
|
mMapCanvas->zoomToNextExtent();
|
|
}
|
|
|
|
void QgisApp::zoomActualSize()
|
|
{
|
|
legendLayerZoomNative();
|
|
}
|
|
|
|
void QgisApp::identify()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mIdentify );
|
|
}
|
|
|
|
void QgisApp::doFeatureAction()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mFeatureAction );
|
|
}
|
|
|
|
void QgisApp::updateDefaultFeatureAction( QAction *action )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( !vlayer )
|
|
return;
|
|
|
|
mActionFeatureAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mAction.svg" ) ) );
|
|
mActionFeatureAction->setToolTip( tr( "No action selected" ) );
|
|
|
|
mFeatureActionMenu->setActiveAction( action );
|
|
|
|
QgsAction qgsAction;
|
|
if ( action )
|
|
{
|
|
qgsAction = action->data().value<QgsAction>();
|
|
}
|
|
|
|
if ( qgsAction.isValid() )
|
|
{
|
|
vlayer->actions()->setDefaultAction( QStringLiteral( "Canvas" ), qgsAction.id() );
|
|
QgsGui::mapLayerActionRegistry()->setDefaultActionForLayer( vlayer, nullptr );
|
|
|
|
mActionFeatureAction->setToolTip( tr( "Run feature action<br><b>%1</b>" ).arg( qgsAction.name() ) );
|
|
|
|
if ( !qgsAction.icon().isNull() )
|
|
mActionFeatureAction->setIcon( qgsAction.icon() );
|
|
}
|
|
else
|
|
{
|
|
//action is from QgsMapLayerActionRegistry
|
|
vlayer->actions()->setDefaultAction( QStringLiteral( "Canvas" ), QString() );
|
|
|
|
QgsMapLayerAction *mapLayerAction = qobject_cast<QgsMapLayerAction *>( action );
|
|
if ( mapLayerAction )
|
|
{
|
|
QgsGui::mapLayerActionRegistry()->setDefaultActionForLayer( vlayer, mapLayerAction );
|
|
|
|
if ( !mapLayerAction->text().isEmpty() )
|
|
mActionFeatureAction->setToolTip( tr( "Run feature action<br><b>%1</b>" ).arg( mapLayerAction->text() ) );
|
|
|
|
if ( !mapLayerAction->icon().isNull() )
|
|
mActionFeatureAction->setIcon( mapLayerAction->icon() );
|
|
}
|
|
else
|
|
{
|
|
QgsGui::mapLayerActionRegistry()->setDefaultActionForLayer( vlayer, nullptr );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::refreshFeatureActions()
|
|
{
|
|
mFeatureActionMenu->clear();
|
|
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( !vlayer )
|
|
return;
|
|
|
|
QList<QgsAction> actions = vlayer->actions()->actions( QStringLiteral( "Canvas" ) );
|
|
Q_FOREACH ( const QgsAction &action, actions )
|
|
{
|
|
QAction *qAction = new QAction( action.icon(), action.shortTitle(), mFeatureActionMenu );
|
|
qAction->setData( QVariant::fromValue<QgsAction>( action ) );
|
|
mFeatureActionMenu->addAction( qAction );
|
|
|
|
if ( action.name() == vlayer->actions()->defaultAction( QStringLiteral( "Canvas" ) ).name() )
|
|
{
|
|
mFeatureActionMenu->setActiveAction( qAction );
|
|
}
|
|
}
|
|
|
|
//add actions registered in QgsMapLayerActionRegistry
|
|
QList<QgsMapLayerAction *> registeredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( vlayer );
|
|
if ( !actions.isEmpty() && !registeredActions.empty() )
|
|
{
|
|
//add a separator between user defined and standard actions
|
|
mFeatureActionMenu->addSeparator();
|
|
}
|
|
|
|
for ( int i = 0; i < registeredActions.size(); i++ )
|
|
{
|
|
mFeatureActionMenu->addAction( registeredActions.at( i ) );
|
|
if ( registeredActions.at( i ) == QgsGui::mapLayerActionRegistry()->defaultActionForLayer( vlayer ) )
|
|
{
|
|
mFeatureActionMenu->setActiveAction( registeredActions.at( i ) );
|
|
}
|
|
}
|
|
|
|
updateDefaultFeatureAction( mFeatureActionMenu->activeAction() );
|
|
}
|
|
|
|
void QgisApp::measure()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mMeasureDist );
|
|
}
|
|
|
|
void QgisApp::measureArea()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mMeasureArea );
|
|
}
|
|
|
|
void QgisApp::measureAngle()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mMeasureAngle );
|
|
}
|
|
|
|
void QgisApp::addFormAnnotation()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mFormAnnotation );
|
|
}
|
|
|
|
void QgisApp::addHtmlAnnotation()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mHtmlAnnotation );
|
|
}
|
|
|
|
void QgisApp::addTextAnnotation()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mTextAnnotation );
|
|
}
|
|
|
|
void QgisApp::addSvgAnnotation()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mSvgAnnotation );
|
|
}
|
|
|
|
void QgisApp::modifyAnnotation()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mAnnotation );
|
|
}
|
|
|
|
void QgisApp::reprojectAnnotations()
|
|
{
|
|
Q_FOREACH ( QgsMapCanvasAnnotationItem *annotation, annotationItems() )
|
|
{
|
|
annotation->updatePosition();
|
|
}
|
|
}
|
|
|
|
void QgisApp::labelingFontNotFound( QgsVectorLayer *vlayer, const QString &fontfamily )
|
|
{
|
|
// TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
|
|
QString substitute = tr( "Default system font substituted." );
|
|
|
|
QToolButton *btnOpenPrefs = new QToolButton();
|
|
btnOpenPrefs->setStyleSheet( QStringLiteral( "QToolButton{ background-color: rgba(255, 255, 255, 0); color: black; text-decoration: underline; }" ) );
|
|
btnOpenPrefs->setCursor( Qt::PointingHandCursor );
|
|
btnOpenPrefs->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
|
|
btnOpenPrefs->setToolButtonStyle( Qt::ToolButtonTextOnly );
|
|
|
|
// store pointer to vlayer in data of QAction
|
|
QAction *act = new QAction( btnOpenPrefs );
|
|
act->setData( QVariant( QMetaType::QObjectStar, &vlayer ) );
|
|
act->setText( tr( "Open labeling dialog" ) );
|
|
btnOpenPrefs->addAction( act );
|
|
btnOpenPrefs->setDefaultAction( act );
|
|
btnOpenPrefs->setToolTip( QLatin1String( "" ) );
|
|
connect( btnOpenPrefs, &QToolButton::triggered, this, &QgisApp::labelingDialogFontNotFound );
|
|
|
|
// no timeout set, since notice needs attention and is only shown first time layer is labeled
|
|
QgsMessageBarItem *fontMsg = new QgsMessageBarItem(
|
|
tr( "Labeling" ),
|
|
tr( "Font for layer <b><u>%1</u></b> was not found (<i>%2</i>). %3" ).arg( vlayer->name(), fontfamily, substitute ),
|
|
btnOpenPrefs,
|
|
QgsMessageBar::WARNING,
|
|
0,
|
|
messageBar() );
|
|
messageBar()->pushItem( fontMsg );
|
|
}
|
|
|
|
void QgisApp::commitError( QgsVectorLayer *vlayer )
|
|
{
|
|
QgsMessageViewer *mv = new QgsMessageViewer();
|
|
mv->setWindowTitle( tr( "Commit Errors" ) );
|
|
mv->setMessageAsPlainText( tr( "Could not commit changes to layer %1" ).arg( vlayer->name() )
|
|
+ "\n\n"
|
|
+ tr( "Errors: %1\n" ).arg( vlayer->commitErrors().join( QStringLiteral( "\n " ) ) )
|
|
);
|
|
|
|
QToolButton *showMore = new QToolButton();
|
|
// store pointer to vlayer in data of QAction
|
|
QAction *act = new QAction( showMore );
|
|
act->setData( QVariant( QMetaType::QObjectStar, &vlayer ) );
|
|
act->setText( tr( "Show more" ) );
|
|
showMore->setStyleSheet( QStringLiteral( "background-color: rgba(255, 255, 255, 0); color: black; text-decoration: underline;" ) );
|
|
showMore->setCursor( Qt::PointingHandCursor );
|
|
showMore->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
|
|
showMore->addAction( act );
|
|
showMore->setDefaultAction( act );
|
|
connect( showMore, &QToolButton::triggered, mv, &QDialog::exec );
|
|
connect( showMore, &QToolButton::triggered, showMore, &QObject::deleteLater );
|
|
|
|
// no timeout set, since notice needs attention and is only shown first time layer is labeled
|
|
QgsMessageBarItem *errorMsg = new QgsMessageBarItem(
|
|
tr( "Commit errors" ),
|
|
tr( "Could not commit changes to layer %1" ).arg( vlayer->name() ),
|
|
showMore,
|
|
QgsMessageBar::WARNING,
|
|
0,
|
|
messageBar() );
|
|
messageBar()->pushItem( errorMsg );
|
|
}
|
|
|
|
void QgisApp::labelingDialogFontNotFound( QAction *act )
|
|
{
|
|
if ( !act )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// get base pointer to layer
|
|
QObject *obj = qvariant_cast<QObject *>( act->data() );
|
|
|
|
// remove calling messagebar widget
|
|
messageBar()->popWidget();
|
|
|
|
if ( !obj )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( obj );
|
|
if ( layer && setActiveLayer( layer ) )
|
|
{
|
|
labeling();
|
|
}
|
|
}
|
|
|
|
void QgisApp::labeling()
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( !vlayer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mapStyleDock( true );
|
|
mMapStyleWidget->setCurrentPage( QgsLayerStylingWidget::VectorLabeling );
|
|
}
|
|
|
|
void QgisApp::setMapStyleDockLayer( QgsMapLayer *layer )
|
|
{
|
|
if ( !layer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mMapStyleWidget->setEnabled( true );
|
|
// We don't set the layer if the dock isn't open mainly to save
|
|
// the extra work if it's not needed
|
|
if ( mMapStylingDock->isVisible() )
|
|
{
|
|
mMapStyleWidget->setLayer( layer );
|
|
}
|
|
}
|
|
|
|
void QgisApp::mapStyleDock( bool enabled )
|
|
{
|
|
mMapStylingDock->setUserVisible( enabled );
|
|
setMapStyleDockLayer( activeLayer() );
|
|
}
|
|
|
|
void QgisApp::diagramProperties()
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( !vlayer )
|
|
{
|
|
messageBar()->pushMessage( tr( "Diagram Properties" ),
|
|
tr( "Please select a vector layer first" ),
|
|
QgsMessageBar::INFO,
|
|
messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
QDialog dlg;
|
|
dlg.setWindowTitle( tr( "Layer Diagram Properties" ) );
|
|
QgsDiagramProperties *gui = new QgsDiagramProperties( vlayer, &dlg, mMapCanvas );
|
|
gui->layout()->setContentsMargins( 0, 0, 0, 0 );
|
|
QVBoxLayout *layout = new QVBoxLayout( &dlg );
|
|
layout->addWidget( gui );
|
|
|
|
QDialogButtonBox *buttonBox = new QDialogButtonBox(
|
|
QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply,
|
|
Qt::Horizontal, &dlg );
|
|
layout->addWidget( buttonBox );
|
|
|
|
dlg.setLayout( layout );
|
|
|
|
connect( buttonBox->button( QDialogButtonBox::Ok ), &QAbstractButton::clicked,
|
|
&dlg, &QDialog::accept );
|
|
connect( buttonBox->button( QDialogButtonBox::Cancel ), &QAbstractButton::clicked,
|
|
&dlg, &QDialog::reject );
|
|
connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked,
|
|
gui, &QgsDiagramProperties::apply );
|
|
|
|
if ( dlg.exec() )
|
|
gui->apply();
|
|
|
|
activateDeactivateLayerRelatedActions( vlayer );
|
|
}
|
|
|
|
void QgisApp::setCadDockVisible( bool visible )
|
|
{
|
|
mAdvancedDigitizingDockWidget->setVisible( visible );
|
|
}
|
|
|
|
void QgisApp::fieldCalculator()
|
|
{
|
|
QgsVectorLayer *myLayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( !myLayer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsFieldCalculator calc( myLayer, this );
|
|
if ( calc.exec() )
|
|
{
|
|
myLayer->triggerRepaint();
|
|
}
|
|
}
|
|
|
|
void QgisApp::attributeTable()
|
|
{
|
|
QgsVectorLayer *myLayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( !myLayer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsAttributeTableDialog *mDialog = new QgsAttributeTableDialog( myLayer );
|
|
mDialog->show();
|
|
// the dialog will be deleted by itself on close
|
|
}
|
|
|
|
void QgisApp::saveAsRasterFile()
|
|
{
|
|
QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( activeLayer() );
|
|
if ( !rasterLayer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsRasterLayerSaveAsDialog d( rasterLayer, rasterLayer->dataProvider(),
|
|
mMapCanvas->extent(), rasterLayer->crs(),
|
|
mMapCanvas->mapSettings().destinationCrs(),
|
|
this );
|
|
if ( d.exec() == QDialog::Rejected )
|
|
return;
|
|
|
|
QgsSettings settings;
|
|
settings.setValue( QStringLiteral( "UI/lastRasterFileDir" ), QFileInfo( d.outputFileName() ).absolutePath() );
|
|
|
|
QgsRasterFileWriter fileWriter( d.outputFileName() );
|
|
if ( d.tileMode() )
|
|
{
|
|
fileWriter.setTiledMode( true );
|
|
fileWriter.setMaxTileWidth( d.maximumTileSizeX() );
|
|
fileWriter.setMaxTileHeight( d.maximumTileSizeY() );
|
|
}
|
|
|
|
// TODO: show error dialogs
|
|
// TODO: this code should go somewhere else, but probably not into QgsRasterFileWriter
|
|
// clone pipe/provider is not really necessary, ready for threads
|
|
std::unique_ptr<QgsRasterPipe> pipe( nullptr );
|
|
|
|
if ( d.mode() == QgsRasterLayerSaveAsDialog::RawDataMode )
|
|
{
|
|
QgsDebugMsg( "Writing raw data" );
|
|
//QgsDebugMsg( QString( "Writing raw data" ).arg( pos ) );
|
|
pipe.reset( new QgsRasterPipe() );
|
|
if ( !pipe->set( rasterLayer->dataProvider()->clone() ) )
|
|
{
|
|
QgsDebugMsg( "Cannot set pipe provider" );
|
|
return;
|
|
}
|
|
|
|
QgsRasterNuller *nuller = new QgsRasterNuller();
|
|
for ( int band = 1; band <= rasterLayer->dataProvider()->bandCount(); band ++ )
|
|
{
|
|
nuller->setNoData( band, d.noData() );
|
|
}
|
|
if ( !pipe->insert( 1, nuller ) )
|
|
{
|
|
QgsDebugMsg( "Cannot set pipe nuller" );
|
|
return;
|
|
}
|
|
|
|
// add projector if necessary
|
|
if ( d.outputCrs() != rasterLayer->crs() )
|
|
{
|
|
QgsRasterProjector *projector = new QgsRasterProjector;
|
|
projector->setCrs( rasterLayer->crs(), d.outputCrs() );
|
|
if ( !pipe->insert( 2, projector ) )
|
|
{
|
|
QgsDebugMsg( "Cannot set pipe projector" );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else // RenderedImageMode
|
|
{
|
|
// clone the whole pipe
|
|
QgsDebugMsg( "Writing rendered image" );
|
|
pipe.reset( new QgsRasterPipe( *rasterLayer->pipe() ) );
|
|
QgsRasterProjector *projector = pipe->projector();
|
|
if ( !projector )
|
|
{
|
|
QgsDebugMsg( "Cannot get pipe projector" );
|
|
return;
|
|
}
|
|
projector->setCrs( rasterLayer->crs(), d.outputCrs() );
|
|
}
|
|
|
|
if ( !pipe->last() )
|
|
{
|
|
return;
|
|
}
|
|
fileWriter.setCreateOptions( d.createOptions() );
|
|
|
|
fileWriter.setBuildPyramidsFlag( d.buildPyramidsFlag() );
|
|
fileWriter.setPyramidsList( d.pyramidsList() );
|
|
fileWriter.setPyramidsResampling( d.pyramidsResamplingMethod() );
|
|
fileWriter.setPyramidsFormat( d.pyramidsFormat() );
|
|
fileWriter.setPyramidsConfigOptions( d.pyramidsConfigOptions() );
|
|
|
|
bool tileMode = d.tileMode();
|
|
bool addToCanvas = d.addToCanvas();
|
|
QPointer< QgsRasterLayer > rlWeakPointer( rasterLayer );
|
|
|
|
QgsRasterFileWriterTask *writerTask = new QgsRasterFileWriterTask( fileWriter, pipe.release(), d.nColumns(), d.nRows(),
|
|
d.outputRectangle(), d.outputCrs() );
|
|
|
|
// when writer is successful:
|
|
|
|
connect( writerTask, &QgsRasterFileWriterTask::writeComplete, this, [this, tileMode, addToCanvas, rlWeakPointer ]( const QString & newFilename )
|
|
{
|
|
QString fileName = newFilename;
|
|
if ( tileMode )
|
|
{
|
|
QFileInfo outputInfo( fileName );
|
|
fileName = QStringLiteral( "%1/%2.vrt" ).arg( fileName, outputInfo.fileName() );
|
|
}
|
|
|
|
if ( addToCanvas )
|
|
{
|
|
addRasterLayers( QStringList( fileName ) );
|
|
}
|
|
if ( rlWeakPointer )
|
|
emit layerSavedAs( rlWeakPointer, fileName );
|
|
|
|
messageBar()->pushMessage( tr( "Saving done" ),
|
|
tr( "Export to raster file has been completed" ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
} );
|
|
|
|
// when an error occurs:
|
|
connect( writerTask, &QgsRasterFileWriterTask::errorOccurred, this, [ = ]( int error )
|
|
{
|
|
if ( error != QgsRasterFileWriter::WriteCanceled )
|
|
{
|
|
QMessageBox::warning( this, tr( "Error" ),
|
|
tr( "Cannot write raster error code: %1" ).arg( error ),
|
|
QMessageBox::Ok );
|
|
}
|
|
} );
|
|
|
|
QgsApplication::taskManager()->addTask( writerTask );
|
|
}
|
|
|
|
|
|
void QgisApp::saveAsFile()
|
|
{
|
|
QgsMapLayer *layer = activeLayer();
|
|
if ( !layer )
|
|
return;
|
|
|
|
QgsMapLayer::LayerType layerType = layer->type();
|
|
if ( layerType == QgsMapLayer::RasterLayer )
|
|
{
|
|
saveAsRasterFile();
|
|
}
|
|
else if ( layerType == QgsMapLayer::VectorLayer )
|
|
{
|
|
saveAsVectorFileGeneral();
|
|
}
|
|
}
|
|
|
|
void QgisApp::saveAsLayerDefinition()
|
|
{
|
|
QString path = QFileDialog::getSaveFileName( this, QStringLiteral( "Save as Layer Definition File" ), QDir::home().path(), QStringLiteral( "*.qlr" ) );
|
|
QgsDebugMsg( path );
|
|
if ( path.isEmpty() )
|
|
return;
|
|
|
|
QString errorMessage;
|
|
bool saved = QgsLayerDefinition::exportLayerDefinition( path, mLayerTreeView->selectedNodes(), errorMessage );
|
|
if ( !saved )
|
|
{
|
|
messageBar()->pushMessage( tr( "Error saving layer definition file" ), errorMessage, QgsMessageBar::WARNING );
|
|
}
|
|
}
|
|
|
|
///@cond PRIVATE
|
|
|
|
/** Field value converter for export as vector layer
|
|
* \note Not available in Python bindings
|
|
*/
|
|
class QgisAppFieldValueConverter : public QgsVectorFileWriter::FieldValueConverter
|
|
{
|
|
public:
|
|
QgisAppFieldValueConverter( QgsVectorLayer *vl, const QgsAttributeList &attributesAsDisplayedValues );
|
|
|
|
QgsField fieldDefinition( const QgsField &field ) override;
|
|
|
|
QVariant convert( int idx, const QVariant &value ) override;
|
|
|
|
QgisAppFieldValueConverter *clone() const override;
|
|
|
|
private:
|
|
QPointer< QgsVectorLayer > mLayer;
|
|
QgsAttributeList mAttributesAsDisplayedValues;
|
|
};
|
|
|
|
QgisAppFieldValueConverter::QgisAppFieldValueConverter( QgsVectorLayer *vl, const QgsAttributeList &attributesAsDisplayedValues )
|
|
: mLayer( vl )
|
|
, mAttributesAsDisplayedValues( attributesAsDisplayedValues )
|
|
{
|
|
}
|
|
|
|
QgsField QgisAppFieldValueConverter::fieldDefinition( const QgsField &field )
|
|
{
|
|
if ( !mLayer )
|
|
return field;
|
|
|
|
int idx = mLayer->fields().indexFromName( field.name() );
|
|
if ( mAttributesAsDisplayedValues.contains( idx ) )
|
|
{
|
|
return QgsField( field.name(), QVariant::String );
|
|
}
|
|
return field;
|
|
}
|
|
|
|
QVariant QgisAppFieldValueConverter::convert( int idx, const QVariant &value )
|
|
{
|
|
if ( !mLayer || !mAttributesAsDisplayedValues.contains( idx ) )
|
|
{
|
|
return value;
|
|
}
|
|
const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, mLayer->fields().field( idx ).name() );
|
|
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
|
|
return fieldFormatter->representValue( mLayer, idx, setup.config(), QVariant(), value );
|
|
}
|
|
|
|
QgisAppFieldValueConverter *QgisAppFieldValueConverter::clone() const
|
|
{
|
|
return new QgisAppFieldValueConverter( *this );
|
|
}
|
|
|
|
///@endcond
|
|
|
|
void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer *vlayer, bool symbologyOption )
|
|
{
|
|
if ( !vlayer )
|
|
{
|
|
vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() ); // FIXME: output of multiple layers at once?
|
|
}
|
|
|
|
if ( !vlayer )
|
|
return;
|
|
|
|
QgsCoordinateReferenceSystem destCRS;
|
|
|
|
int options = QgsVectorLayerSaveAsDialog::AllOptions;
|
|
if ( !symbologyOption )
|
|
{
|
|
options &= ~QgsVectorLayerSaveAsDialog::Symbology;
|
|
}
|
|
|
|
QgsVectorLayerSaveAsDialog *dialog = new QgsVectorLayerSaveAsDialog( vlayer, options, this );
|
|
|
|
dialog->setMapCanvas( mMapCanvas );
|
|
dialog->setIncludeZ( QgsWkbTypes::hasZ( vlayer->wkbType() ) );
|
|
|
|
if ( dialog->exec() == QDialog::Accepted )
|
|
{
|
|
QString encoding = dialog->encoding();
|
|
QString vectorFilename = dialog->filename();
|
|
QString format = dialog->format();
|
|
QStringList datasourceOptions = dialog->datasourceOptions();
|
|
bool autoGeometryType = dialog->automaticGeometryType();
|
|
QgsWkbTypes::Type forcedGeometryType = dialog->geometryType();
|
|
|
|
QgsCoordinateTransform ct;
|
|
destCRS = QgsCoordinateReferenceSystem::fromSrsId( dialog->crs() );
|
|
|
|
if ( destCRS.isValid() && destCRS != vlayer->crs() )
|
|
{
|
|
ct = QgsCoordinateTransform( vlayer->crs(), destCRS );
|
|
|
|
//ask user about datum transformation
|
|
QgsSettings settings;
|
|
QList< QList< int > > dt = QgsCoordinateTransform::datumTransformations( vlayer->crs(), destCRS );
|
|
if ( dt.size() > 1 && settings.value( QStringLiteral( "Projections/showDatumTransformDialog" ), false ).toBool() )
|
|
{
|
|
QgsDatumTransformDialog d( vlayer->name(), dt );
|
|
if ( d.exec() == QDialog::Accepted )
|
|
{
|
|
QList< int > sdt = d.selectedDatumTransform();
|
|
if ( !sdt.isEmpty() )
|
|
{
|
|
ct.setSourceDatumTransform( sdt.at( 0 ) );
|
|
}
|
|
if ( sdt.size() > 1 )
|
|
{
|
|
ct.setDestinationDatumTransform( sdt.at( 1 ) );
|
|
}
|
|
ct.initialize();
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsRectangle filterExtent = dialog->filterExtent();
|
|
QgisAppFieldValueConverter converter( vlayer, dialog->attributesAsDisplayedValues() );
|
|
QgisAppFieldValueConverter *converterPtr = nullptr;
|
|
// No need to use the converter if there is nothing to convert
|
|
if ( !dialog->attributesAsDisplayedValues().isEmpty() )
|
|
converterPtr = &converter;
|
|
|
|
QgsVectorFileWriter::SaveVectorOptions options;
|
|
options.driverName = format;
|
|
options.layerName = dialog->layername();
|
|
options.actionOnExistingFile = dialog->creationActionOnExistingFile();
|
|
options.fileEncoding = encoding;
|
|
options.ct = ct;
|
|
options.onlySelectedFeatures = dialog->onlySelected();
|
|
options.datasourceOptions = datasourceOptions;
|
|
options.layerOptions = dialog->layerOptions();
|
|
options.skipAttributeCreation = dialog->selectedAttributes().isEmpty();
|
|
options.symbologyExport = static_cast< QgsVectorFileWriter::SymbologyExport >( dialog->symbologyExport() );
|
|
options.symbologyScale = dialog->scale();
|
|
if ( dialog->hasFilterExtent() )
|
|
options.filterExtent = filterExtent;
|
|
options.overrideGeometryType = autoGeometryType ? QgsWkbTypes::Unknown : forcedGeometryType;
|
|
options.forceMulti = dialog->forceMulti();
|
|
options.includeZ = dialog->includeZ();
|
|
options.attributes = dialog->selectedAttributes();
|
|
options.fieldValueConverter = converterPtr;
|
|
|
|
bool addToCanvas = dialog->addToCanvas();
|
|
QString layerName = dialog->layername();
|
|
QgsVectorFileWriterTask *writerTask = new QgsVectorFileWriterTask( vlayer, vectorFilename, options );
|
|
|
|
// when writer is successful:
|
|
connect( writerTask, &QgsVectorFileWriterTask::writeComplete, this, [this, addToCanvas, layerName, encoding, vectorFilename, vlayer]( const QString & newFilename )
|
|
{
|
|
if ( addToCanvas )
|
|
{
|
|
QString uri( newFilename );
|
|
if ( !layerName.isEmpty() )
|
|
uri += "|layername=" + layerName;
|
|
this->addVectorLayers( QStringList( uri ), encoding, QStringLiteral( "file" ) );
|
|
}
|
|
this->emit layerSavedAs( vlayer, vectorFilename );
|
|
this->messageBar()->pushMessage( tr( "Saving done" ),
|
|
tr( "Export to vector file has been completed" ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
}
|
|
);
|
|
|
|
// when an error occurs:
|
|
connect( writerTask, &QgsVectorFileWriterTask::errorOccurred, this, [ = ]( int error, const QString & errorMessage )
|
|
{
|
|
if ( error != QgsVectorFileWriter::Canceled )
|
|
{
|
|
QgsMessageViewer *m = new QgsMessageViewer( nullptr );
|
|
m->setWindowTitle( tr( "Save Error" ) );
|
|
m->setMessageAsPlainText( tr( "Export to vector file failed.\nError: %1" ).arg( errorMessage ) );
|
|
m->exec();
|
|
}
|
|
}
|
|
);
|
|
|
|
QgsApplication::taskManager()->addTask( writerTask );
|
|
}
|
|
|
|
delete dialog;
|
|
}
|
|
|
|
void QgisApp::layerProperties()
|
|
{
|
|
showLayerProperties( activeLayer() );
|
|
}
|
|
|
|
void QgisApp::deleteSelected( QgsMapLayer *layer, QWidget *parent, bool promptConfirmation )
|
|
{
|
|
if ( !layer )
|
|
{
|
|
layer = mLayerTreeView->currentLayer();
|
|
}
|
|
|
|
if ( !parent )
|
|
{
|
|
parent = this;
|
|
}
|
|
|
|
if ( !layer )
|
|
{
|
|
messageBar()->pushMessage( tr( "No Layer Selected" ),
|
|
tr( "To delete features, you must select a vector layer in the legend" ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
|
|
if ( !vlayer )
|
|
{
|
|
messageBar()->pushMessage( tr( "No Vector Layer Selected" ),
|
|
tr( "Deleting features only works on vector layers" ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
if ( !( vlayer->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteFeatures ) )
|
|
{
|
|
messageBar()->pushMessage( tr( "Provider does not support deletion" ),
|
|
tr( "Data provider does not support deleting features" ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
if ( !vlayer->isEditable() )
|
|
{
|
|
messageBar()->pushMessage( tr( "Layer not editable" ),
|
|
tr( "The current layer is not editable. Choose 'Start editing' in the digitizing toolbar." ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
//validate selection
|
|
int numberOfSelectedFeatures = vlayer->selectedFeatureIds().size();
|
|
if ( numberOfSelectedFeatures == 0 )
|
|
{
|
|
messageBar()->pushMessage( tr( "No Features Selected" ),
|
|
tr( "The current layer has no selected features" ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
//display a warning
|
|
if ( promptConfirmation && QMessageBox::warning( parent, tr( "Delete features" ), tr( "Delete %n feature(s)?", "number of features to delete", numberOfSelectedFeatures ), QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel )
|
|
{
|
|
return;
|
|
}
|
|
|
|
vlayer->beginEditCommand( tr( "Features deleted" ) );
|
|
int deletedCount = 0;
|
|
if ( !vlayer->deleteSelectedFeatures( &deletedCount ) )
|
|
{
|
|
messageBar()->pushMessage( tr( "Problem deleting features" ),
|
|
tr( "A problem occurred during deletion of %1 feature(s)" ).arg( numberOfSelectedFeatures - deletedCount ),
|
|
QgsMessageBar::WARNING );
|
|
}
|
|
else
|
|
{
|
|
showStatusMessage( tr( "%n feature(s) deleted.", "number of features deleted", numberOfSelectedFeatures ) );
|
|
}
|
|
|
|
vlayer->endEditCommand();
|
|
}
|
|
|
|
void QgisApp::moveFeature()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mMoveFeature );
|
|
}
|
|
|
|
void QgisApp::moveFeatureCopy()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mMoveFeatureCopy );
|
|
}
|
|
|
|
void QgisApp::offsetCurve()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mOffsetCurve );
|
|
}
|
|
|
|
void QgisApp::simplifyFeature()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mSimplifyFeature );
|
|
}
|
|
|
|
void QgisApp::deleteRing()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mDeleteRing );
|
|
}
|
|
|
|
void QgisApp::deletePart()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mDeletePart );
|
|
}
|
|
|
|
QgsGeometry QgisApp::unionGeometries( const QgsVectorLayer *vl, QgsFeatureList &featureList, bool &canceled )
|
|
{
|
|
canceled = false;
|
|
if ( !vl || featureList.size() < 2 )
|
|
{
|
|
return QgsGeometry();
|
|
}
|
|
|
|
if ( !featureList.at( 0 ).hasGeometry() )
|
|
return QgsGeometry();
|
|
|
|
QgsGeometry unionGeom = featureList.at( 0 ).geometry();
|
|
|
|
QProgressDialog progress( tr( "Merging features..." ), tr( "Abort" ), 0, featureList.size(), this );
|
|
progress.setWindowModality( Qt::WindowModal );
|
|
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
|
|
for ( int i = 1; i < featureList.size(); ++i )
|
|
{
|
|
if ( progress.wasCanceled() )
|
|
{
|
|
QApplication::restoreOverrideCursor();
|
|
canceled = true;
|
|
return QgsGeometry();
|
|
}
|
|
progress.setValue( i );
|
|
QgsGeometry currentGeom = featureList.at( i ).geometry();
|
|
if ( !currentGeom.isNull() )
|
|
{
|
|
unionGeom = unionGeom.combine( currentGeom );
|
|
if ( unionGeom.isNull() )
|
|
{
|
|
QApplication::restoreOverrideCursor();
|
|
return QgsGeometry();
|
|
}
|
|
}
|
|
}
|
|
|
|
//convert unionGeom to a multipart geometry in case it is necessary to match the layer type
|
|
if ( QgsWkbTypes::isMultiType( vl->wkbType() ) && !unionGeom.isMultipart() )
|
|
{
|
|
unionGeom.convertToMultiType();
|
|
}
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
progress.setValue( featureList.size() );
|
|
return unionGeom;
|
|
}
|
|
|
|
bool QgisApp::uniqueComposerTitle( QWidget *parent, QString &composerTitle, bool acceptEmpty, const QString ¤tName )
|
|
{
|
|
if ( !parent )
|
|
{
|
|
parent = this;
|
|
}
|
|
bool ok = false;
|
|
bool titleValid = false;
|
|
QString newTitle = QString( currentName );
|
|
QString chooseMsg = tr( "Create unique print composer title" );
|
|
if ( acceptEmpty )
|
|
{
|
|
chooseMsg += '\n' + tr( "(title generated if left empty)" );
|
|
}
|
|
QString titleMsg = chooseMsg;
|
|
|
|
QStringList cNames;
|
|
cNames << newTitle;
|
|
Q_FOREACH ( QgsComposition *c, QgsProject::instance()->layoutManager()->compositions() )
|
|
{
|
|
cNames << c->name();
|
|
}
|
|
|
|
while ( !titleValid )
|
|
{
|
|
newTitle = QInputDialog::getText( parent,
|
|
tr( "Composer title" ),
|
|
titleMsg,
|
|
QLineEdit::Normal,
|
|
newTitle,
|
|
&ok );
|
|
if ( !ok )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( newTitle.isEmpty() )
|
|
{
|
|
if ( !acceptEmpty )
|
|
{
|
|
titleMsg = chooseMsg + "\n\n" + tr( "Title can not be empty!" );
|
|
}
|
|
else
|
|
{
|
|
titleValid = true;
|
|
newTitle = QgsProject::instance()->layoutManager()->generateUniqueTitle();
|
|
}
|
|
}
|
|
else if ( cNames.indexOf( newTitle, 1 ) >= 0 )
|
|
{
|
|
cNames[0] = QString(); // clear non-unique name
|
|
titleMsg = chooseMsg + "\n\n" + tr( "Title already exists!" );
|
|
}
|
|
else
|
|
{
|
|
titleValid = true;
|
|
}
|
|
}
|
|
|
|
composerTitle = newTitle;
|
|
|
|
return true;
|
|
}
|
|
|
|
QgsComposer *QgisApp::createNewComposer( QString title )
|
|
{
|
|
if ( title.isEmpty() )
|
|
{
|
|
title = QgsProject::instance()->layoutManager()->generateUniqueTitle();
|
|
}
|
|
//create new composition object
|
|
QgsComposition *composition = new QgsComposition( QgsProject::instance() );
|
|
composition->setName( title );
|
|
QgsProject::instance()->layoutManager()->addComposition( composition );
|
|
return openComposer( composition );
|
|
}
|
|
|
|
QgsComposer *QgisApp::openComposer( QgsComposition *composition )
|
|
{
|
|
// maybe a composer already open for this composition
|
|
Q_FOREACH ( QgsComposer *composer, mPrintComposers )
|
|
{
|
|
if ( composer->composition() == composition )
|
|
{
|
|
composer->show();
|
|
composer->activate();
|
|
composer->raise();
|
|
return composer;
|
|
}
|
|
}
|
|
|
|
//nope, so make a new one
|
|
QgsComposer *newComposerObject = new QgsComposer( composition );
|
|
connect( newComposerObject, &QgsComposer::aboutToClose, this, [this, newComposerObject]
|
|
{
|
|
emit composerWillBeClosed( newComposerObject->iface() );
|
|
mPrintComposers.remove( newComposerObject );
|
|
emit composerClosed( newComposerObject->iface() );
|
|
} );
|
|
|
|
//add it to the map of existing print composers
|
|
mPrintComposers.insert( newComposerObject );
|
|
|
|
newComposerObject->open();
|
|
emit composerOpened( newComposerObject->iface() );
|
|
connect( newComposerObject, &QgsComposer::atlasPreviewFeatureChanged, this, &QgisApp::refreshMapCanvas );
|
|
|
|
return newComposerObject;
|
|
}
|
|
|
|
QgsLayoutDesignerDialog *QgisApp::openLayoutDesignerDialog( QgsLayout *layout )
|
|
{
|
|
// maybe a designer already open for this layout
|
|
Q_FOREACH ( QgsLayoutDesignerDialog *designer, mLayoutDesignerDialogs )
|
|
{
|
|
if ( designer->currentLayout() == layout )
|
|
{
|
|
designer->show();
|
|
designer->activate();
|
|
designer->raise();
|
|
return designer;
|
|
}
|
|
}
|
|
|
|
//nope, so make a new one
|
|
QgsLayoutDesignerDialog *newDesigner = new QgsLayoutDesignerDialog( this );
|
|
newDesigner->setCurrentLayout( layout );
|
|
connect( newDesigner, &QgsLayoutDesignerDialog::aboutToClose, this, [this, newDesigner]
|
|
{
|
|
emit layoutDesignerWillBeClosed( newDesigner->iface() );
|
|
mLayoutDesignerDialogs.remove( newDesigner );
|
|
emit layoutDesignerClosed();
|
|
} );
|
|
|
|
//add it to the map of existing print designers
|
|
mLayoutDesignerDialogs.insert( newDesigner );
|
|
|
|
newDesigner->open();
|
|
emit layoutDesignerOpened( newDesigner->iface() );
|
|
//connect( newDesigner, &QgsLayoutDesignerDialog::atlasPreviewFeatureChanged, this, &QgisApp::refreshMapCanvas );
|
|
|
|
return newDesigner;
|
|
}
|
|
|
|
void QgisApp::deleteComposer( QgsComposer *c )
|
|
{
|
|
emit composerWillBeClosed( c->iface() );
|
|
mPrintComposers.remove( c );
|
|
emit composerClosed( c->iface() );
|
|
delete c;
|
|
}
|
|
|
|
QgsComposer *QgisApp::duplicateComposer( QgsComposer *currentComposer, QString title )
|
|
{
|
|
if ( title.isEmpty() )
|
|
{
|
|
// TODO: inject a bit of randomness in auto-titles?
|
|
title = currentComposer->composition()->name() + tr( " copy" );
|
|
}
|
|
|
|
QgsComposition *newComposition = QgsProject::instance()->layoutManager()->duplicateComposition( currentComposer->composition()->name(),
|
|
title );
|
|
QgsComposer *newComposer = openComposer( newComposition );
|
|
if ( !newComposer )
|
|
{
|
|
QgsDebugMsg( "could not create new composer" );
|
|
return newComposer;
|
|
}
|
|
newComposer->activate();
|
|
return newComposer;
|
|
}
|
|
|
|
void QgisApp::deletePrintComposers()
|
|
{
|
|
QSet<QgsComposer *>::iterator it = mPrintComposers.begin();
|
|
while ( it != mPrintComposers.end() )
|
|
{
|
|
QgsComposer *c = ( *it );
|
|
emit composerWillBeClosed( c->iface() );
|
|
it = mPrintComposers.erase( it );
|
|
emit composerClosed( c->iface() );
|
|
delete ( c );
|
|
}
|
|
}
|
|
|
|
void QgisApp::setupLayoutManagerConnections()
|
|
{
|
|
QgsLayoutManager *manager = QgsProject::instance()->layoutManager();
|
|
connect( manager, &QgsLayoutManager::compositionAdded, this, [ = ]( const QString & name )
|
|
{
|
|
QgsComposition *c = QgsProject::instance()->layoutManager()->compositionByName( name );
|
|
if ( !c )
|
|
return;
|
|
|
|
mAtlasFeatureActions.insert( c, nullptr );
|
|
connect( c, &QgsComposition::nameChanged, this, [this, c]( const QString & name )
|
|
{
|
|
QgsMapLayerAction *action = mAtlasFeatureActions.value( c );
|
|
if ( action )
|
|
{
|
|
action->setText( QString( tr( "Set as atlas feature for %1" ) ).arg( name ) );
|
|
}
|
|
} );
|
|
|
|
connect( &c->atlasComposition(), &QgsAtlasComposition::coverageLayerChanged, this, [this, c]( QgsVectorLayer * coverageLayer )
|
|
{
|
|
setupAtlasMapLayerAction( c, static_cast< bool >( coverageLayer ) );
|
|
} );
|
|
|
|
connect( &c->atlasComposition(), &QgsAtlasComposition::toggled, this, [this, c]( bool enabled )
|
|
{
|
|
setupAtlasMapLayerAction( c, enabled );
|
|
} );
|
|
|
|
setupAtlasMapLayerAction( c, c->atlasComposition().enabled() && c->atlasComposition().coverageLayer() );
|
|
} );
|
|
|
|
connect( manager, &QgsLayoutManager::compositionAboutToBeRemoved, this, [ = ]( const QString & name )
|
|
{
|
|
QgsComposition *c = QgsProject::instance()->layoutManager()->compositionByName( name );
|
|
if ( c )
|
|
{
|
|
QgsMapLayerAction *action = mAtlasFeatureActions.value( c );
|
|
if ( action )
|
|
{
|
|
QgsGui::mapLayerActionRegistry()->removeMapLayerAction( action );
|
|
delete action;
|
|
mAtlasFeatureActions.remove( c );
|
|
}
|
|
}
|
|
} );
|
|
}
|
|
|
|
void QgisApp::setupAtlasMapLayerAction( QgsComposition *composition, bool enableAction )
|
|
{
|
|
QgsMapLayerAction *action = mAtlasFeatureActions.value( composition );
|
|
if ( action )
|
|
{
|
|
QgsGui::mapLayerActionRegistry()->removeMapLayerAction( action );
|
|
delete action;
|
|
action = nullptr;
|
|
mAtlasFeatureActions.remove( composition );
|
|
}
|
|
|
|
if ( enableAction )
|
|
{
|
|
action = new QgsMapLayerAction( QString( tr( "Set as atlas feature for %1" ) ).arg( composition->name() ),
|
|
this, composition->atlasComposition().coverageLayer(), QgsMapLayerAction::SingleFeature,
|
|
QgsApplication::getThemeIcon( QStringLiteral( "/mIconAtlas.svg" ) ) );
|
|
mAtlasFeatureActions.insert( composition, action );
|
|
QgsGui::mapLayerActionRegistry()->addMapLayerAction( action );
|
|
connect( action, &QgsMapLayerAction::triggeredForFeature, this, [this, composition]( QgsMapLayer * layer, const QgsFeature & feat )
|
|
{
|
|
setCompositionAtlasFeature( composition, layer, feat );
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
void QgisApp::setCompositionAtlasFeature( QgsComposition *composition, QgsMapLayer *layer, const QgsFeature &feat )
|
|
{
|
|
QgsComposer *composer = openComposer( composition );
|
|
composer->setAtlasFeature( layer, feat );
|
|
}
|
|
|
|
void QgisApp::composerMenuAboutToShow()
|
|
{
|
|
populateComposerMenu( mPrintComposersMenu );
|
|
}
|
|
|
|
void QgisApp::populateComposerMenu( QMenu *menu )
|
|
{
|
|
menu->clear();
|
|
QList<QAction *> acts;
|
|
Q_FOREACH ( QgsComposition *c, QgsProject::instance()->layoutManager()->compositions() )
|
|
{
|
|
QAction *a = new QAction( c->name(), menu );
|
|
connect( a, &QAction::triggered, this, [this, c]
|
|
{
|
|
openComposer( c );
|
|
} );
|
|
acts << a;
|
|
}
|
|
if ( acts.size() > 1 )
|
|
{
|
|
// sort actions by text
|
|
std::sort( acts.begin(), acts.end(), cmpByText_ );
|
|
}
|
|
menu->addActions( acts );
|
|
}
|
|
|
|
void QgisApp::compositionAboutToBeRemoved( const QString &name )
|
|
{
|
|
Q_FOREACH ( QgsComposer *composer, mPrintComposers )
|
|
{
|
|
if ( composer->composition()->name() == name )
|
|
{
|
|
emit composerWillBeClosed( composer->iface() );
|
|
mPrintComposers.remove( composer );
|
|
emit composerClosed( composer->iface() );
|
|
delete composer;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::showPinnedLabels( bool show )
|
|
{
|
|
qobject_cast<QgsMapToolPinLabels *>( mMapTools.mPinLabels )->showPinnedLabels( show );
|
|
}
|
|
|
|
void QgisApp::pinLabels()
|
|
{
|
|
mActionShowPinnedLabels->setChecked( true );
|
|
mMapCanvas->setMapTool( mMapTools.mPinLabels );
|
|
}
|
|
|
|
void QgisApp::showHideLabels()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mShowHideLabels );
|
|
}
|
|
|
|
void QgisApp::moveLabel()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mMoveLabel );
|
|
}
|
|
|
|
void QgisApp::rotateFeature()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mRotateFeature );
|
|
}
|
|
|
|
void QgisApp::rotateLabel()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mRotateLabel );
|
|
}
|
|
|
|
void QgisApp::changeLabelProperties()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mChangeLabelProperties );
|
|
}
|
|
|
|
QList<QgsMapCanvasAnnotationItem *> QgisApp::annotationItems()
|
|
{
|
|
QList<QgsMapCanvasAnnotationItem *> itemList;
|
|
|
|
if ( !mMapCanvas )
|
|
{
|
|
return itemList;
|
|
}
|
|
|
|
if ( mMapCanvas )
|
|
{
|
|
QList<QGraphicsItem *> graphicsItems = mMapCanvas->items();
|
|
QList<QGraphicsItem *>::iterator gIt = graphicsItems.begin();
|
|
for ( ; gIt != graphicsItems.end(); ++gIt )
|
|
{
|
|
QgsMapCanvasAnnotationItem *currentItem = dynamic_cast<QgsMapCanvasAnnotationItem *>( *gIt );
|
|
if ( currentItem )
|
|
{
|
|
itemList.push_back( currentItem );
|
|
}
|
|
}
|
|
}
|
|
return itemList;
|
|
}
|
|
|
|
QList<QgsMapCanvas *> QgisApp::mapCanvases()
|
|
{
|
|
return findChildren< QgsMapCanvas * >();
|
|
}
|
|
|
|
void QgisApp::removeAnnotationItems()
|
|
{
|
|
if ( !mMapCanvas )
|
|
{
|
|
return;
|
|
}
|
|
QGraphicsScene *scene = mMapCanvas->scene();
|
|
if ( !scene )
|
|
{
|
|
return;
|
|
}
|
|
QList<QgsMapCanvasAnnotationItem *> itemList = annotationItems();
|
|
Q_FOREACH ( QgsMapCanvasAnnotationItem *item, itemList )
|
|
{
|
|
if ( item )
|
|
{
|
|
scene->removeItem( item );
|
|
delete item;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::mergeAttributesOfSelectedFeatures()
|
|
{
|
|
//get active layer (hopefully vector)
|
|
QgsMapLayer *activeMapLayer = activeLayer();
|
|
if ( !activeMapLayer )
|
|
{
|
|
messageBar()->pushMessage( tr( "No active layer" ),
|
|
tr( "No active layer found. Please select a layer in the layer list" ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( activeMapLayer );
|
|
if ( !vl )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Layer not editable" ),
|
|
tr( "The merge features tool only works on vector layers." ),
|
|
QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
if ( !vl->isEditable() )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Layer not editable" ),
|
|
tr( "Merging features can only be done for layers in editing mode." ),
|
|
QgsMessageBar::WARNING );
|
|
|
|
return;
|
|
}
|
|
|
|
//get selected feature ids (as a QSet<int> )
|
|
const QgsFeatureIds &featureIdSet = vl->selectedFeatureIds();
|
|
if ( featureIdSet.size() < 2 )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Not enough features selected" ),
|
|
tr( "The merge tool requires at least two selected features" ),
|
|
QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
//get initial selection (may be altered by attribute merge dialog later)
|
|
QgsFeatureList featureList = vl->selectedFeatures(); //get QList<QgsFeature>
|
|
|
|
//merge the attributes together
|
|
QgsMergeAttributesDialog d( featureList, vl, mapCanvas() );
|
|
//initialize dialog with all columns set to skip
|
|
d.setAllToSkip();
|
|
if ( d.exec() == QDialog::Rejected )
|
|
{
|
|
return;
|
|
}
|
|
|
|
vl->beginEditCommand( tr( "Merged feature attributes" ) );
|
|
|
|
QgsAttributes merged = d.mergedAttributes();
|
|
QSet<int> toSkip = d.skippedAttributeIndexes();
|
|
|
|
bool firstFeature = true;
|
|
Q_FOREACH ( QgsFeatureId fid, vl->selectedFeatureIds() )
|
|
{
|
|
for ( int i = 0; i < merged.count(); ++i )
|
|
{
|
|
if ( toSkip.contains( i ) )
|
|
continue;
|
|
|
|
QVariant val = merged.at( i );
|
|
QgsField fld( vl->fields().at( i ) );
|
|
bool isDefaultValue = vl->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
|
|
vl->dataProvider() &&
|
|
vl->dataProvider()->defaultValueClause( vl->fields().fieldOriginIndex( i ) ) == val;
|
|
|
|
// convert to destination data type
|
|
if ( !isDefaultValue && !fld.convertCompatible( val ) )
|
|
{
|
|
if ( firstFeature )
|
|
{
|
|
//only warn on first feature
|
|
messageBar()->pushMessage(
|
|
tr( "Invalid result" ),
|
|
tr( "Could not store value '%1' in field of type %2" ).arg( merged.at( i ).toString(), fld.typeName() ),
|
|
QgsMessageBar::WARNING );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vl->changeAttributeValue( fid, i, val );
|
|
}
|
|
}
|
|
firstFeature = false;
|
|
}
|
|
|
|
vl->endEditCommand();
|
|
|
|
vl->triggerRepaint();
|
|
}
|
|
|
|
void QgisApp::modifyAttributesOfSelectedFeatures()
|
|
{
|
|
QgsMapLayer *activeMapLayer = activeLayer();
|
|
if ( !activeMapLayer )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "No active layer" ),
|
|
tr( "Please select a layer in the layer list" ),
|
|
QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( activeMapLayer );
|
|
if ( !vl )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Invalid layer" ),
|
|
tr( "The merge features tool only works on vector layers." ),
|
|
QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
if ( !vl->isEditable() )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Layer not editable" ),
|
|
tr( "Modifying features can only be done for layers in editing mode." ),
|
|
QgsMessageBar::WARNING );
|
|
|
|
return;
|
|
}
|
|
|
|
//dummy feature
|
|
QgsFeature f;
|
|
QgsAttributeEditorContext context;
|
|
context.setAllowCustomUi( false );
|
|
|
|
QgsAttributeDialog *dialog = new QgsAttributeDialog( vl, &f, false, this, true, context );
|
|
dialog->setMode( QgsAttributeForm::MultiEditMode );
|
|
dialog->exec();
|
|
}
|
|
|
|
void QgisApp::mergeSelectedFeatures()
|
|
{
|
|
//get active layer (hopefully vector)
|
|
QgsMapLayer *activeMapLayer = activeLayer();
|
|
if ( !activeMapLayer )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "No active layer" ),
|
|
tr( "Please select a layer in the layer list" ),
|
|
QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( activeMapLayer );
|
|
if ( !vl )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Invalid layer" ),
|
|
tr( "The merge features tool only works on vector layers." ),
|
|
QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
if ( !vl->isEditable() )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Layer not editable" ),
|
|
tr( "Merging features can only be done for layers in editing mode." ),
|
|
QgsMessageBar::WARNING );
|
|
|
|
return;
|
|
}
|
|
|
|
//get selected feature ids (as a QSet<int> )
|
|
const QgsFeatureIds &featureIdSet = vl->selectedFeatureIds();
|
|
if ( featureIdSet.size() < 2 )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Not enough features selected" ),
|
|
tr( "The merge tool requires at least two selected features" ),
|
|
QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
//get initial selection (may be altered by attribute merge dialog later)
|
|
QgsFeatureIds featureIds = vl->selectedFeatureIds();
|
|
QgsFeatureList featureList = vl->selectedFeatures(); //get QList<QgsFeature>
|
|
bool canceled;
|
|
QgsGeometry unionGeom = unionGeometries( vl, featureList, canceled );
|
|
if ( unionGeom.isNull() )
|
|
{
|
|
if ( !canceled )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Merge failed" ),
|
|
tr( "An error occurred during the merge operation" ),
|
|
QgsMessageBar::CRITICAL );
|
|
}
|
|
return;
|
|
}
|
|
|
|
//merge the attributes together
|
|
QgsMergeAttributesDialog d( featureList, vl, mapCanvas() );
|
|
if ( d.exec() == QDialog::Rejected )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsFeatureIds featureIdsAfter = vl->selectedFeatureIds();
|
|
|
|
if ( featureIdsAfter.size() < 2 )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Not enough features selected" ),
|
|
tr( "The merge tool requires at least two selected features" ),
|
|
QgsMessageBar::WARNING );
|
|
return;
|
|
}
|
|
|
|
//if the user changed the feature selection in the merge dialog, we need to repeat the union and check the type
|
|
if ( featureIds.size() != featureIdsAfter.size() )
|
|
{
|
|
bool canceled;
|
|
QgsFeatureList featureListAfter = vl->selectedFeatures();
|
|
unionGeom = unionGeometries( vl, featureListAfter, canceled );
|
|
if ( unionGeom.isNull() )
|
|
{
|
|
if ( !canceled )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Merge failed" ),
|
|
tr( "An error occurred during the merge operation" ),
|
|
QgsMessageBar::CRITICAL );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
vl->beginEditCommand( tr( "Merged features" ) );
|
|
|
|
//create new feature
|
|
QgsFeature newFeature;
|
|
newFeature.setGeometry( unionGeom );
|
|
|
|
QgsAttributes attrs = d.mergedAttributes();
|
|
for ( int i = 0; i < attrs.count(); ++i )
|
|
{
|
|
QVariant val = attrs.at( i );
|
|
bool isDefaultValue = vl->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
|
|
vl->dataProvider() &&
|
|
vl->dataProvider()->defaultValueClause( vl->fields().fieldOriginIndex( i ) ) == val;
|
|
|
|
// convert to destination data type
|
|
if ( !isDefaultValue && !vl->fields().at( i ).convertCompatible( val ) )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "Invalid result" ),
|
|
tr( "Could not store value '%1' in field of type %2" ).arg( attrs.at( i ).toString(), vl->fields().at( i ).typeName() ),
|
|
QgsMessageBar::WARNING );
|
|
}
|
|
attrs[i] = val;
|
|
}
|
|
newFeature.setAttributes( attrs );
|
|
|
|
QgsFeatureIds::const_iterator feature_it = featureIdsAfter.constBegin();
|
|
for ( ; feature_it != featureIdsAfter.constEnd(); ++feature_it )
|
|
{
|
|
vl->deleteFeature( *feature_it );
|
|
}
|
|
|
|
vl->addFeature( newFeature );
|
|
|
|
vl->endEditCommand();
|
|
|
|
vl->triggerRepaint();
|
|
}
|
|
|
|
void QgisApp::nodeTool()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mNodeTool );
|
|
}
|
|
|
|
void QgisApp::rotatePointSymbols()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mRotatePointSymbolsTool );
|
|
}
|
|
|
|
void QgisApp::offsetPointSymbol()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mOffsetPointSymbolTool );
|
|
}
|
|
|
|
void QgisApp::snappingOptions()
|
|
{
|
|
mSnappingDialogContainer->show();
|
|
}
|
|
|
|
void QgisApp::splitFeatures()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mSplitFeatures );
|
|
}
|
|
|
|
void QgisApp::splitParts()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mSplitParts );
|
|
}
|
|
|
|
void QgisApp::reshapeFeatures()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mReshapeFeatures );
|
|
}
|
|
|
|
void QgisApp::addFeature()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mAddFeature );
|
|
}
|
|
|
|
void QgisApp::setMapTool( QgsMapTool *tool )
|
|
{
|
|
mMapCanvas->setMapTool( tool );
|
|
}
|
|
|
|
void QgisApp::selectFeatures()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mSelectFeatures );
|
|
}
|
|
|
|
void QgisApp::selectByPolygon()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mSelectPolygon );
|
|
}
|
|
|
|
void QgisApp::selectByFreehand()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mSelectFreehand );
|
|
}
|
|
|
|
void QgisApp::selectByRadius()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mSelectRadius );
|
|
}
|
|
|
|
void QgisApp::deselectAll()
|
|
{
|
|
// Turn off rendering to improve speed.
|
|
bool wasFrozen = mMapCanvas->isFrozen();
|
|
freezeCanvases();
|
|
|
|
QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
|
|
for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
|
|
{
|
|
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
|
|
if ( !vl )
|
|
continue;
|
|
|
|
vl->removeSelection();
|
|
}
|
|
|
|
// Turn on rendering (if it was on previously)
|
|
if ( !wasFrozen )
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
}
|
|
|
|
void QgisApp::invertSelection()
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
|
|
if ( !vlayer )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "No active vector layer" ),
|
|
tr( "To invert selection, choose a vector layer in the legend" ),
|
|
QgsMessageBar::INFO,
|
|
messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
vlayer->invertSelection();
|
|
}
|
|
|
|
void QgisApp::selectAll()
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
|
|
if ( !vlayer )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "No active vector layer" ),
|
|
tr( "To select all, choose a vector layer in the legend" ),
|
|
QgsMessageBar::INFO,
|
|
messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
// Turn off rendering to improve speed.
|
|
bool wasFrozen = mMapCanvas->isFrozen();
|
|
freezeCanvases();
|
|
|
|
vlayer->selectAll();
|
|
|
|
// Turn on rendering (if it was on previously)
|
|
if ( !wasFrozen )
|
|
freezeCanvases( false );
|
|
}
|
|
|
|
void QgisApp::selectByExpression()
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
|
|
if ( !vlayer )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "No active vector layer" ),
|
|
tr( "To select features, choose a vector layer in the legend" ),
|
|
QgsMessageBar::INFO,
|
|
messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
QgsExpressionSelectionDialog *dlg = new QgsExpressionSelectionDialog( vlayer, QString(), this );
|
|
dlg->setMessageBar( messageBar() );
|
|
dlg->setMapCanvas( mapCanvas() );
|
|
dlg->setAttribute( Qt::WA_DeleteOnClose );
|
|
dlg->show();
|
|
}
|
|
|
|
void QgisApp::selectByForm()
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mMapCanvas->currentLayer() );
|
|
if ( !vlayer )
|
|
{
|
|
messageBar()->pushMessage(
|
|
tr( "No active vector layer" ),
|
|
tr( "To select features, choose a vector layer in the legend" ),
|
|
QgsMessageBar::INFO,
|
|
messageTimeout() );
|
|
return;
|
|
}
|
|
QgsDistanceArea myDa;
|
|
|
|
myDa.setSourceCrs( vlayer->crs() );
|
|
myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
|
|
|
|
QgsAttributeEditorContext context;
|
|
context.setDistanceArea( myDa );
|
|
context.setVectorLayerTools( mVectorLayerTools );
|
|
|
|
QgsSelectByFormDialog *dlg = new QgsSelectByFormDialog( vlayer, context, this );
|
|
dlg->setMessageBar( messageBar() );
|
|
dlg->setMapCanvas( mapCanvas() );
|
|
dlg->setAttribute( Qt::WA_DeleteOnClose );
|
|
dlg->show();
|
|
}
|
|
|
|
void QgisApp::addRing()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mAddRing );
|
|
}
|
|
|
|
void QgisApp::fillRing()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mFillRing );
|
|
}
|
|
|
|
|
|
void QgisApp::addPart()
|
|
{
|
|
mMapCanvas->setMapTool( mMapTools.mAddPart );
|
|
}
|
|
|
|
|
|
void QgisApp::editCut( QgsMapLayer *layerContainingSelection )
|
|
{
|
|
// Test for feature support in this layer
|
|
QgsVectorLayer *selectionVectorLayer = qobject_cast<QgsVectorLayer *>( layerContainingSelection ? layerContainingSelection : activeLayer() );
|
|
if ( !selectionVectorLayer )
|
|
return;
|
|
|
|
clipboard()->replaceWithCopyOf( selectionVectorLayer );
|
|
|
|
selectionVectorLayer->beginEditCommand( tr( "Features cut" ) );
|
|
selectionVectorLayer->deleteSelectedFeatures();
|
|
selectionVectorLayer->endEditCommand();
|
|
}
|
|
|
|
void QgisApp::editCopy( QgsMapLayer *layerContainingSelection )
|
|
{
|
|
QgsVectorLayer *selectionVectorLayer = qobject_cast<QgsVectorLayer *>( layerContainingSelection ? layerContainingSelection : activeLayer() );
|
|
if ( !selectionVectorLayer )
|
|
return;
|
|
|
|
// Test for feature support in this layer
|
|
clipboard()->replaceWithCopyOf( selectionVectorLayer );
|
|
}
|
|
|
|
void QgisApp::clipboardChanged()
|
|
{
|
|
activateDeactivateLayerRelatedActions( activeLayer() );
|
|
}
|
|
|
|
void QgisApp::editPaste( QgsMapLayer *destinationLayer )
|
|
{
|
|
QgsVectorLayer *pasteVectorLayer = qobject_cast<QgsVectorLayer *>( destinationLayer ? destinationLayer : activeLayer() );
|
|
if ( !pasteVectorLayer )
|
|
return;
|
|
|
|
pasteVectorLayer->beginEditCommand( tr( "Features pasted" ) );
|
|
QgsFeatureList features = clipboard()->transformedCopyOf( pasteVectorLayer->crs(), pasteVectorLayer->fields() );
|
|
int nTotalFeatures = features.count();
|
|
|
|
QHash<int, int> remap;
|
|
QgsFields fields = clipboard()->fields();
|
|
for ( int idx = 0; idx < fields.count(); ++idx )
|
|
{
|
|
int dst = pasteVectorLayer->fields().lookupField( fields.at( idx ).name() );
|
|
if ( dst < 0 )
|
|
continue;
|
|
|
|
remap.insert( idx, dst );
|
|
}
|
|
|
|
QgsExpressionContext context = pasteVectorLayer->createExpressionContext();
|
|
|
|
|
|
QgsFeatureList newFeatures;
|
|
QgsFeatureList::const_iterator featureIt = features.constBegin();
|
|
while ( featureIt != features.constEnd() )
|
|
{
|
|
QgsAttributes srcAttr = featureIt->attributes();
|
|
QgsAttributeMap dstAttr;
|
|
|
|
for ( int src = 0; src < srcAttr.count(); ++src )
|
|
{
|
|
int dst = remap.value( src, -1 );
|
|
if ( dst < 0 )
|
|
continue;
|
|
|
|
dstAttr[ dst ] = srcAttr.at( src );
|
|
}
|
|
|
|
QgsGeometry geom = featureIt->geometry();
|
|
if ( featureIt->hasGeometry() )
|
|
{
|
|
// convert geometry to match destination layer
|
|
QgsWkbTypes::GeometryType destType = pasteVectorLayer->geometryType();
|
|
bool destIsMulti = QgsWkbTypes::isMultiType( pasteVectorLayer->wkbType() );
|
|
if ( pasteVectorLayer->dataProvider() &&
|
|
!pasteVectorLayer->dataProvider()->doesStrictFeatureTypeCheck() )
|
|
{
|
|
// force destination to multi if provider doesn't do a feature strict check
|
|
destIsMulti = true;
|
|
}
|
|
|
|
if ( destType != QgsWkbTypes::UnknownGeometry )
|
|
{
|
|
QgsGeometry newGeometry = geom.convertToType( destType, destIsMulti );
|
|
if ( newGeometry.isNull() )
|
|
{
|
|
continue;
|
|
}
|
|
geom = newGeometry;
|
|
}
|
|
// avoid intersection if enabled in digitize settings
|
|
geom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers() );
|
|
}
|
|
|
|
// now create new feature using pasted feature as a template. This automatically handles default
|
|
// values and field constraints
|
|
newFeatures << QgsVectorLayerUtils::createFeature( pasteVectorLayer, geom, dstAttr, &context );
|
|
++featureIt;
|
|
}
|
|
|
|
pasteVectorLayer->addFeatures( newFeatures );
|
|
QgsFeatureIds newIds;
|
|
for ( const QgsFeature &f : qgsAsConst( newFeatures ) )
|
|
{
|
|
newIds << f.id();
|
|
}
|
|
|
|
|
|
pasteVectorLayer->selectByIds( newIds );
|
|
pasteVectorLayer->endEditCommand();
|
|
pasteVectorLayer->updateExtents();
|
|
|
|
int nCopiedFeatures = features.count();
|
|
if ( nCopiedFeatures == 0 )
|
|
{
|
|
messageBar()->pushMessage( tr( "Paste features" ),
|
|
tr( "no features could be successfully pasted." ),
|
|
QgsMessageBar::WARNING, messageTimeout() );
|
|
|
|
}
|
|
else if ( nCopiedFeatures == nTotalFeatures )
|
|
{
|
|
messageBar()->pushMessage( tr( "Paste features" ),
|
|
tr( "%1 features were successfully pasted." ).arg( nCopiedFeatures ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
}
|
|
else
|
|
{
|
|
messageBar()->pushMessage( tr( "Paste features" ),
|
|
tr( "%1 of %2 features could be successfully pasted." ).arg( nCopiedFeatures ).arg( nTotalFeatures ),
|
|
QgsMessageBar::WARNING, messageTimeout() );
|
|
}
|
|
|
|
pasteVectorLayer->triggerRepaint();
|
|
}
|
|
|
|
void QgisApp::pasteAsNewVector()
|
|
{
|
|
|
|
QgsVectorLayer *layer = pasteToNewMemoryVector();
|
|
if ( !layer )
|
|
return;
|
|
|
|
saveAsVectorFileGeneral( layer, false );
|
|
|
|
delete layer;
|
|
}
|
|
|
|
QgsVectorLayer *QgisApp::pasteAsNewMemoryVector( const QString &layerName )
|
|
{
|
|
|
|
QString layerNameCopy = layerName;
|
|
|
|
if ( layerNameCopy.isEmpty() )
|
|
{
|
|
bool ok;
|
|
QString defaultName = tr( "Pasted" );
|
|
layerNameCopy = QInputDialog::getText( this, tr( "New temporary scratch layer name" ),
|
|
tr( "Layer name" ), QLineEdit::Normal,
|
|
defaultName, &ok );
|
|
if ( !ok )
|
|
return nullptr;
|
|
|
|
if ( layerNameCopy.isEmpty() )
|
|
{
|
|
layerNameCopy = defaultName;
|
|
}
|
|
}
|
|
|
|
QgsVectorLayer *layer = pasteToNewMemoryVector();
|
|
if ( !layer )
|
|
return nullptr;
|
|
|
|
layer->setName( layerNameCopy );
|
|
|
|
freezeCanvases();
|
|
|
|
QgsProject::instance()->addMapLayer( layer );
|
|
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
|
|
return layer;
|
|
}
|
|
|
|
QgsVectorLayer *QgisApp::pasteToNewMemoryVector()
|
|
{
|
|
QgsFields fields = clipboard()->fields();
|
|
|
|
// Decide geometry type from features, switch to multi type if at least one multi is found
|
|
QMap<QgsWkbTypes::Type, int> typeCounts;
|
|
QgsFeatureList features = clipboard()->copyOf( fields );
|
|
for ( int i = 0; i < features.size(); i++ )
|
|
{
|
|
QgsFeature &feature = features[i];
|
|
if ( !feature.hasGeometry() )
|
|
continue;
|
|
|
|
QgsWkbTypes::Type type = feature.geometry().wkbType();
|
|
|
|
if ( type == QgsWkbTypes::Unknown || type == QgsWkbTypes::NoGeometry )
|
|
continue;
|
|
|
|
if ( QgsWkbTypes::isSingleType( type ) )
|
|
{
|
|
if ( typeCounts.contains( QgsWkbTypes::multiType( type ) ) )
|
|
{
|
|
typeCounts[ QgsWkbTypes::multiType( type )] = typeCounts[ QgsWkbTypes::multiType( type )] + 1;
|
|
}
|
|
else
|
|
{
|
|
typeCounts[ type ] = typeCounts[ type ] + 1;
|
|
}
|
|
}
|
|
else if ( QgsWkbTypes::isMultiType( type ) )
|
|
{
|
|
if ( typeCounts.contains( QgsWkbTypes::singleType( type ) ) )
|
|
{
|
|
// switch to multi type
|
|
typeCounts[type] = typeCounts[ QgsWkbTypes::singleType( type )];
|
|
typeCounts.remove( QgsWkbTypes::singleType( type ) );
|
|
}
|
|
typeCounts[type] = typeCounts[type] + 1;
|
|
}
|
|
}
|
|
|
|
QgsWkbTypes::Type wkbType = !typeCounts.isEmpty() ? typeCounts.keys().value( 0 ) : QgsWkbTypes::NoGeometry;
|
|
|
|
if ( features.isEmpty() )
|
|
{
|
|
// should not happen
|
|
messageBar()->pushMessage( tr( "Paste features" ),
|
|
tr( "No features in clipboard." ),
|
|
QgsMessageBar::WARNING, messageTimeout() );
|
|
return nullptr;
|
|
}
|
|
else if ( typeCounts.size() > 1 )
|
|
{
|
|
QString typeName = wkbType != QgsWkbTypes::NoGeometry ? QgsWkbTypes::displayString( wkbType ) : QStringLiteral( "none" );
|
|
messageBar()->pushMessage( tr( "Paste features" ),
|
|
tr( "Multiple geometry types found, features with geometry different from %1 will be created without geometry." ).arg( typeName ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
}
|
|
|
|
QgsVectorLayer *layer = QgsMemoryProviderUtils::createMemoryLayer( QStringLiteral( "pasted_features" ), QgsFields(), wkbType, clipboard()->crs() );
|
|
|
|
if ( !layer->isValid() || !layer->dataProvider() )
|
|
{
|
|
delete layer;
|
|
messageBar()->pushMessage( tr( "Paste features" ),
|
|
tr( "Cannot create new layer." ),
|
|
QgsMessageBar::WARNING, messageTimeout() );
|
|
return nullptr;
|
|
}
|
|
|
|
layer->startEditing();
|
|
Q_FOREACH ( QgsField f, clipboard()->fields().toList() )
|
|
{
|
|
QgsDebugMsg( QString( "field %1 (%2)" ).arg( f.name(), QVariant::typeToName( f.type() ) ) );
|
|
if ( !layer->addAttribute( f ) )
|
|
{
|
|
messageBar()->pushMessage( tr( "Paste features" ),
|
|
tr( "Cannot create field %1 (%2,%3)" ).arg( f.name(), f.typeName(), QVariant::typeToName( f.type() ) ),
|
|
QgsMessageBar::WARNING, messageTimeout() );
|
|
delete layer;
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Convert to multi if necessary
|
|
for ( int i = 0; i < features.size(); i++ )
|
|
{
|
|
QgsFeature &feature = features[i];
|
|
if ( !feature.hasGeometry() )
|
|
continue;
|
|
|
|
QgsWkbTypes::Type type = feature.geometry().wkbType();
|
|
if ( type == QgsWkbTypes::Unknown || type == QgsWkbTypes::NoGeometry )
|
|
continue;
|
|
|
|
if ( QgsWkbTypes::singleType( wkbType ) != QgsWkbTypes::singleType( type ) )
|
|
{
|
|
feature.clearGeometry();
|
|
}
|
|
|
|
if ( QgsWkbTypes::isMultiType( wkbType ) && QgsWkbTypes::isSingleType( type ) )
|
|
{
|
|
QgsGeometry g = feature.geometry();
|
|
g.convertToMultiType();
|
|
feature.setGeometry( g );
|
|
}
|
|
}
|
|
if ( ! layer->addFeatures( features ) || !layer->commitChanges() )
|
|
{
|
|
QgsDebugMsg( "Cannot add features or commit changes" );
|
|
delete layer;
|
|
return nullptr;
|
|
}
|
|
|
|
QgsDebugMsg( QString( "%1 features pasted to temporary scratch layer" ).arg( layer->featureCount() ) );
|
|
return layer;
|
|
}
|
|
|
|
void QgisApp::copyStyle( QgsMapLayer *sourceLayer )
|
|
{
|
|
QgsMapLayer *selectionLayer = sourceLayer ? sourceLayer : activeLayer();
|
|
|
|
if ( selectionLayer )
|
|
{
|
|
QString errorMsg;
|
|
QDomDocument doc( QStringLiteral( "qgis" ) );
|
|
selectionLayer->exportNamedStyle( doc, errorMsg );
|
|
|
|
|
|
if ( !errorMsg.isEmpty() )
|
|
{
|
|
messageBar()->pushMessage( tr( "Cannot copy style" ),
|
|
errorMsg,
|
|
QgsMessageBar::CRITICAL, messageTimeout() );
|
|
return;
|
|
}
|
|
// Copies data in text form as well, so the XML can be pasted into a text editor
|
|
clipboard()->setData( QGSCLIPBOARD_STYLE_MIME, doc.toByteArray(), doc.toString() );
|
|
// Enables the paste menu element
|
|
mActionPasteStyle->setEnabled( true );
|
|
}
|
|
}
|
|
|
|
/**
|
|
\param destinationLayer The layer that the clipboard will be pasted to
|
|
(defaults to the active layer on the legend)
|
|
*/
|
|
|
|
|
|
void QgisApp::pasteStyle( QgsMapLayer *destinationLayer )
|
|
{
|
|
QgsMapLayer *selectionLayer = destinationLayer ? destinationLayer : activeLayer();
|
|
if ( selectionLayer )
|
|
{
|
|
if ( clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) )
|
|
{
|
|
QDomDocument doc( QStringLiteral( "qgis" ) );
|
|
QString errorMsg;
|
|
int errorLine, errorColumn;
|
|
if ( !doc.setContent( clipboard()->data( QGSCLIPBOARD_STYLE_MIME ), false, &errorMsg, &errorLine, &errorColumn ) )
|
|
{
|
|
|
|
messageBar()->pushMessage( tr( "Cannot parse style" ),
|
|
errorMsg,
|
|
QgsMessageBar::CRITICAL, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
bool isVectorStyle = doc.elementsByTagName( QStringLiteral( "pipe" ) ).isEmpty();
|
|
if ( ( selectionLayer->type() == QgsMapLayer::RasterLayer && isVectorStyle ) ||
|
|
( selectionLayer->type() == QgsMapLayer::VectorLayer && !isVectorStyle ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !selectionLayer->importNamedStyle( doc, errorMsg ) )
|
|
{
|
|
messageBar()->pushMessage( tr( "Cannot paste style" ),
|
|
errorMsg,
|
|
QgsMessageBar::CRITICAL, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
mLayerTreeView->refreshLayerSymbology( selectionLayer->id() );
|
|
selectionLayer->triggerRepaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::copyFeatures( QgsFeatureStore &featureStore )
|
|
{
|
|
clipboard()->replaceWithCopyOf( featureStore );
|
|
}
|
|
|
|
void QgisApp::refreshMapCanvas()
|
|
{
|
|
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
|
|
{
|
|
//stop any current rendering
|
|
canvas->stopRendering();
|
|
canvas->refreshAllLayers();
|
|
}
|
|
}
|
|
|
|
void QgisApp::canvasRefreshStarted()
|
|
{
|
|
mLastRenderTime.restart();
|
|
// if previous render took less than 0.5 seconds, delay the appearance of the
|
|
// render in progress status bar by 0.5 seconds - this avoids the status bar
|
|
// rapidly appearing and then disappearing for very fast renders
|
|
if ( mLastRenderTimeSeconds > 0 && mLastRenderTimeSeconds < 0.5 )
|
|
{
|
|
mRenderProgressBarTimer.setSingleShot( true );
|
|
mRenderProgressBarTimer.setInterval( 500 );
|
|
disconnect( mRenderProgressBarTimerConnection );
|
|
mRenderProgressBarTimerConnection = connect( &mRenderProgressBarTimer, &QTimer::timeout, this, [ = ]()
|
|
{
|
|
showProgress( -1, 0 );
|
|
}
|
|
);
|
|
mRenderProgressBarTimer.start();
|
|
}
|
|
else
|
|
{
|
|
showProgress( -1, 0 ); // trick to make progress bar show busy indicator
|
|
}
|
|
}
|
|
|
|
void QgisApp::canvasRefreshFinished()
|
|
{
|
|
mRenderProgressBarTimer.stop();
|
|
mLastRenderTimeSeconds = mLastRenderTime.elapsed() / 1000.0;
|
|
showProgress( 0, 0 ); // stop the busy indicator
|
|
}
|
|
|
|
void QgisApp::toggleMapTips( bool enabled )
|
|
{
|
|
mMapTipsVisible = enabled;
|
|
// Store if maptips are active
|
|
QgsSettings().setValue( QStringLiteral( "/qgis/enableMapTips" ), mMapTipsVisible );
|
|
|
|
// if off, stop the timer
|
|
if ( !mMapTipsVisible )
|
|
{
|
|
mpMapTipsTimer->stop();
|
|
}
|
|
|
|
if ( mActionMapTips->isChecked() != mMapTipsVisible )
|
|
mActionMapTips->setChecked( mMapTipsVisible );
|
|
}
|
|
|
|
void QgisApp::toggleEditing()
|
|
{
|
|
QgsVectorLayer *currentLayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( currentLayer )
|
|
{
|
|
toggleEditing( currentLayer, true );
|
|
}
|
|
else
|
|
{
|
|
// active although there's no layer active!?
|
|
mActionToggleEditing->setChecked( false );
|
|
}
|
|
}
|
|
|
|
bool QgisApp::toggleEditing( QgsMapLayer *layer, bool allowCancel )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
|
|
if ( !vlayer )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool res = true;
|
|
|
|
QString connString = QgsDataSourceUri( vlayer->source() ).connectionInfo();
|
|
QString key = vlayer->providerType();
|
|
|
|
QMap< QPair< QString, QString>, QgsTransactionGroup *> transactionGroups = QgsProject::instance()->transactionGroups();
|
|
QMap< QPair< QString, QString>, QgsTransactionGroup *>::iterator tIt = transactionGroups .find( qMakePair( key, connString ) );
|
|
QgsTransactionGroup *tg = ( tIt != transactionGroups.end() ? tIt.value() : nullptr );
|
|
|
|
bool isModified = false;
|
|
|
|
// Assume changes if: a) the layer reports modifications or b) its transaction group was modified
|
|
if ( vlayer->isModified() || ( tg && tg->layers().contains( vlayer ) && tg->modified() ) )
|
|
isModified = true;
|
|
|
|
if ( !vlayer->isEditable() && !vlayer->readOnly() )
|
|
{
|
|
if ( !( vlayer->dataProvider()->capabilities() & QgsVectorDataProvider::EditingCapabilities ) )
|
|
{
|
|
mActionToggleEditing->setChecked( false );
|
|
mActionToggleEditing->setEnabled( false );
|
|
messageBar()->pushMessage( tr( "Start editing failed" ),
|
|
tr( "Provider cannot be opened for editing" ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return false;
|
|
}
|
|
|
|
vlayer->startEditing();
|
|
|
|
QgsSettings settings;
|
|
QString markerType = settings.value( QStringLiteral( "qgis/digitizing/marker_style" ), "Cross" ).toString();
|
|
bool markSelectedOnly = settings.value( QStringLiteral( "qgis/digitizing/marker_only_for_selected" ), true ).toBool();
|
|
|
|
// redraw only if markers will be drawn
|
|
if ( ( !markSelectedOnly || vlayer->selectedFeatureCount() > 0 ) &&
|
|
( markerType == QLatin1String( "Cross" ) || markerType == QLatin1String( "SemiTransparentCircle" ) ) )
|
|
{
|
|
vlayer->triggerRepaint();
|
|
}
|
|
}
|
|
else if ( isModified )
|
|
{
|
|
QMessageBox::StandardButtons buttons = QMessageBox::Save | QMessageBox::Discard;
|
|
if ( allowCancel )
|
|
buttons |= QMessageBox::Cancel;
|
|
|
|
switch ( QMessageBox::information( nullptr,
|
|
tr( "Stop editing" ),
|
|
tr( "Do you want to save the changes to layer %1?" ).arg( vlayer->name() ),
|
|
buttons ) )
|
|
{
|
|
case QMessageBox::Cancel:
|
|
res = false;
|
|
break;
|
|
|
|
case QMessageBox::Save:
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
|
|
if ( !vlayer->commitChanges() )
|
|
{
|
|
commitError( vlayer );
|
|
// Leave the in-memory editing state alone,
|
|
// to give the user a chance to enter different values
|
|
// and try the commit again later
|
|
res = false;
|
|
}
|
|
|
|
vlayer->triggerRepaint();
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
break;
|
|
|
|
case QMessageBox::Discard:
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
|
|
freezeCanvases();
|
|
if ( !vlayer->rollBack() )
|
|
{
|
|
messageBar()->pushMessage( tr( "Error" ),
|
|
tr( "Problems during roll back" ),
|
|
QgsMessageBar::CRITICAL );
|
|
res = false;
|
|
}
|
|
freezeCanvases( false );
|
|
|
|
vlayer->triggerRepaint();
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else //layer not modified
|
|
{
|
|
freezeCanvases();
|
|
vlayer->rollBack();
|
|
freezeCanvases( false );
|
|
res = true;
|
|
vlayer->triggerRepaint();
|
|
}
|
|
|
|
if ( !res && layer == activeLayer() )
|
|
{
|
|
// while also called when layer sends editingStarted/editingStopped signals,
|
|
// this ensures correct restoring of gui state if toggling was canceled
|
|
// or layer commit/rollback functions failed
|
|
activateDeactivateLayerRelatedActions( layer );
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void QgisApp::saveActiveLayerEdits()
|
|
{
|
|
saveEdits( activeLayer(), true, true );
|
|
}
|
|
|
|
void QgisApp::saveEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
|
|
if ( !vlayer || !vlayer->isEditable() || !vlayer->isModified() )
|
|
return;
|
|
|
|
if ( vlayer == activeLayer() )
|
|
mSaveRollbackInProgress = true;
|
|
|
|
if ( !vlayer->commitChanges() )
|
|
{
|
|
mSaveRollbackInProgress = false;
|
|
commitError( vlayer );
|
|
}
|
|
|
|
if ( leaveEditable )
|
|
{
|
|
vlayer->startEditing();
|
|
}
|
|
if ( triggerRepaint )
|
|
{
|
|
vlayer->triggerRepaint();
|
|
}
|
|
}
|
|
|
|
void QgisApp::cancelEdits( QgsMapLayer *layer, bool leaveEditable, bool triggerRepaint )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
|
|
if ( !vlayer || !vlayer->isEditable() )
|
|
return;
|
|
|
|
if ( vlayer == activeLayer() && leaveEditable )
|
|
mSaveRollbackInProgress = true;
|
|
|
|
freezeCanvases();
|
|
if ( !vlayer->rollBack( !leaveEditable ) )
|
|
{
|
|
mSaveRollbackInProgress = false;
|
|
QMessageBox::information( nullptr,
|
|
tr( "Error" ),
|
|
tr( "Could not %1 changes to layer %2\n\nErrors: %3\n" )
|
|
.arg( leaveEditable ? tr( "rollback" ) : tr( "cancel" ),
|
|
vlayer->name(),
|
|
vlayer->commitErrors().join( QStringLiteral( "\n " ) ) ) );
|
|
}
|
|
freezeCanvases( false );
|
|
|
|
if ( leaveEditable )
|
|
{
|
|
vlayer->startEditing();
|
|
}
|
|
if ( triggerRepaint )
|
|
{
|
|
vlayer->triggerRepaint();
|
|
}
|
|
}
|
|
|
|
void QgisApp::saveEdits()
|
|
{
|
|
Q_FOREACH ( QgsMapLayer *layer, mLayerTreeView->selectedLayers() )
|
|
{
|
|
saveEdits( layer, true, false );
|
|
}
|
|
refreshMapCanvas();
|
|
activateDeactivateLayerRelatedActions( activeLayer() );
|
|
}
|
|
|
|
void QgisApp::saveAllEdits( bool verifyAction )
|
|
{
|
|
if ( verifyAction )
|
|
{
|
|
if ( !verifyEditsActionDialog( tr( "Save" ), tr( "all" ) ) )
|
|
return;
|
|
}
|
|
|
|
Q_FOREACH ( QgsMapLayer *layer, editableLayers( true ) )
|
|
{
|
|
saveEdits( layer, true, false );
|
|
}
|
|
refreshMapCanvas();
|
|
activateDeactivateLayerRelatedActions( activeLayer() );
|
|
}
|
|
|
|
void QgisApp::rollbackEdits()
|
|
{
|
|
Q_FOREACH ( QgsMapLayer *layer, mLayerTreeView->selectedLayers() )
|
|
{
|
|
cancelEdits( layer, true, false );
|
|
}
|
|
refreshMapCanvas();
|
|
activateDeactivateLayerRelatedActions( activeLayer() );
|
|
}
|
|
|
|
void QgisApp::rollbackAllEdits( bool verifyAction )
|
|
{
|
|
if ( verifyAction )
|
|
{
|
|
if ( !verifyEditsActionDialog( tr( "Rollback" ), tr( "all" ) ) )
|
|
return;
|
|
}
|
|
|
|
Q_FOREACH ( QgsMapLayer *layer, editableLayers( true ) )
|
|
{
|
|
cancelEdits( layer, true, false );
|
|
}
|
|
refreshMapCanvas();
|
|
activateDeactivateLayerRelatedActions( activeLayer() );
|
|
}
|
|
|
|
void QgisApp::cancelEdits()
|
|
{
|
|
Q_FOREACH ( QgsMapLayer *layer, mLayerTreeView->selectedLayers() )
|
|
{
|
|
cancelEdits( layer, false, false );
|
|
}
|
|
refreshMapCanvas();
|
|
activateDeactivateLayerRelatedActions( activeLayer() );
|
|
}
|
|
|
|
void QgisApp::cancelAllEdits( bool verifyAction )
|
|
{
|
|
if ( verifyAction )
|
|
{
|
|
if ( !verifyEditsActionDialog( tr( "Cancel" ), tr( "all" ) ) )
|
|
return;
|
|
}
|
|
|
|
Q_FOREACH ( QgsMapLayer *layer, editableLayers() )
|
|
{
|
|
cancelEdits( layer, false, false );
|
|
}
|
|
refreshMapCanvas();
|
|
activateDeactivateLayerRelatedActions( activeLayer() );
|
|
}
|
|
|
|
bool QgisApp::verifyEditsActionDialog( const QString &act, const QString &upon )
|
|
{
|
|
bool res = false;
|
|
switch ( QMessageBox::information( nullptr,
|
|
tr( "Current edits" ),
|
|
tr( "%1 current changes for %2 layer(s)?" )
|
|
.arg( act,
|
|
upon ),
|
|
QMessageBox::Cancel | QMessageBox::Ok ) )
|
|
{
|
|
case QMessageBox::Ok:
|
|
res = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void QgisApp::updateLayerModifiedActions()
|
|
{
|
|
bool enableSaveLayerEdits = false;
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( vlayer )
|
|
{
|
|
QgsVectorDataProvider *dprovider = vlayer->dataProvider();
|
|
if ( dprovider )
|
|
{
|
|
enableSaveLayerEdits = ( dprovider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues
|
|
&& vlayer->isEditable()
|
|
&& vlayer->isModified() );
|
|
}
|
|
}
|
|
mActionSaveLayerEdits->setEnabled( enableSaveLayerEdits );
|
|
|
|
QList<QgsLayerTreeLayer *> selectedLayerNodes = mLayerTreeView ? mLayerTreeView->selectedLayerNodes() : QList<QgsLayerTreeLayer *>();
|
|
|
|
mActionSaveEdits->setEnabled( QgsLayerTreeUtils::layersModified( selectedLayerNodes ) );
|
|
mActionRollbackEdits->setEnabled( QgsLayerTreeUtils::layersModified( selectedLayerNodes ) );
|
|
mActionCancelEdits->setEnabled( QgsLayerTreeUtils::layersEditable( selectedLayerNodes ) );
|
|
|
|
bool hasEditLayers = !editableLayers().isEmpty();
|
|
mActionAllEdits->setEnabled( hasEditLayers );
|
|
mActionCancelAllEdits->setEnabled( hasEditLayers );
|
|
|
|
bool hasModifiedLayers = !editableLayers( true ).isEmpty();
|
|
mActionSaveAllEdits->setEnabled( hasModifiedLayers );
|
|
mActionRollbackAllEdits->setEnabled( hasModifiedLayers );
|
|
}
|
|
|
|
QList<QgsMapLayer *> QgisApp::editableLayers( bool modified ) const
|
|
{
|
|
QList<QgsMapLayer *> editLayers;
|
|
// use legend layers (instead of registry) so QList mirrors its order
|
|
Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, mLayerTreeView->layerTreeModel()->rootGroup()->findLayers() )
|
|
{
|
|
if ( !nodeLayer->layer() )
|
|
continue;
|
|
|
|
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() );
|
|
if ( !vl )
|
|
continue;
|
|
|
|
if ( vl->isEditable() && ( !modified || vl->isModified() ) )
|
|
editLayers << vl;
|
|
}
|
|
return editLayers;
|
|
}
|
|
|
|
void QgisApp::duplicateVectorStyle( QgsVectorLayer *srcLayer, QgsVectorLayer *destLayer )
|
|
{
|
|
// copy symbology, if possible
|
|
if ( srcLayer->geometryType() == destLayer->geometryType() )
|
|
{
|
|
QDomImplementation DomImplementation;
|
|
QDomDocumentType documentType =
|
|
DomImplementation.createDocumentType(
|
|
QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
|
|
QDomDocument doc( documentType );
|
|
QDomElement rootNode = doc.createElement( QStringLiteral( "qgis" ) );
|
|
rootNode.setAttribute( QStringLiteral( "version" ), Qgis::QGIS_VERSION );
|
|
doc.appendChild( rootNode );
|
|
QString errorMsg;
|
|
srcLayer->writeSymbology( rootNode, doc, errorMsg, QgsReadWriteContext() );
|
|
destLayer->readSymbology( rootNode, errorMsg, QgsReadWriteContext() );
|
|
}
|
|
}
|
|
|
|
void QgisApp::layerSubsetString()
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( !vlayer )
|
|
return;
|
|
|
|
if ( !vlayer->vectorJoins().isEmpty() )
|
|
{
|
|
if ( QMessageBox::question( nullptr, tr( "Filter on joined fields" ),
|
|
tr( "You are about to set a subset filter on a layer that has joined fields. "
|
|
"Joined fields cannot be filtered, unless you convert the layer to a virtual layer first. "
|
|
"Would you like to create a virtual layer out of this layer first?" ),
|
|
QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes )
|
|
{
|
|
QgsVirtualLayerDefinition def = QgsVirtualLayerDefinitionUtils::fromJoinedLayer( vlayer );
|
|
QgsVectorLayer *newLayer = new QgsVectorLayer( def.toString(), vlayer->name() + " (virtual)", QStringLiteral( "virtual" ) );
|
|
if ( newLayer->isValid() )
|
|
{
|
|
duplicateVectorStyle( vlayer, newLayer );
|
|
QgsProject::instance()->addMapLayer( newLayer, /*addToLegend*/ false, /*takeOwnership*/ true );
|
|
QgsLayerTreeUtils::insertLayerBelow( QgsProject::instance()->layerTreeRoot(), vlayer, newLayer );
|
|
mLayerTreeView->setCurrentLayer( newLayer );
|
|
// hide the old layer
|
|
QgsLayerTreeLayer *vLayerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( vlayer->id() );
|
|
if ( vLayerTreeLayer )
|
|
vLayerTreeLayer->setItemVisibilityChecked( false );
|
|
vlayer = newLayer;
|
|
}
|
|
else
|
|
{
|
|
delete newLayer;
|
|
}
|
|
}
|
|
}
|
|
|
|
// launch the query builder
|
|
QgsQueryBuilder *qb = new QgsQueryBuilder( vlayer, this );
|
|
QString subsetBefore = vlayer->subsetString();
|
|
|
|
// Set the sql in the query builder to the same in the prop dialog
|
|
// (in case the user has already changed it)
|
|
qb->setSql( vlayer->subsetString() );
|
|
// Open the query builder
|
|
if ( qb->exec() )
|
|
{
|
|
if ( subsetBefore != qb->sql() )
|
|
{
|
|
vlayer->triggerRepaint();
|
|
if ( mLayerTreeView )
|
|
{
|
|
mLayerTreeView->refreshLayerSymbology( vlayer->id() );
|
|
}
|
|
}
|
|
}
|
|
|
|
// delete the query builder object
|
|
delete qb;
|
|
}
|
|
|
|
void QgisApp::saveLastMousePosition( const QgsPointXY &p )
|
|
{
|
|
if ( mMapTipsVisible )
|
|
{
|
|
// store the point, we need it for when the maptips timer fires
|
|
mLastMapPosition = p;
|
|
|
|
// we use this slot to control the timer for maptips since it is fired each time
|
|
// the mouse moves.
|
|
if ( mMapCanvas->underMouse() )
|
|
{
|
|
// Clear the maptip (this is done conditionally)
|
|
mpMaptip->clear( mMapCanvas );
|
|
// don't start the timer if the mouse is not over the map canvas
|
|
mpMapTipsTimer->start();
|
|
//QgsDebugMsg("Started maptips timer");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void QgisApp::showScale( double scale )
|
|
{
|
|
mScaleWidget->setScale( scale );
|
|
|
|
// Not sure if the lines below do anything meaningful /Homann
|
|
if ( mScaleWidget->width() > mScaleWidget->minimumWidth() )
|
|
{
|
|
mScaleWidget->setMinimumWidth( mScaleWidget->width() );
|
|
}
|
|
}
|
|
|
|
|
|
void QgisApp::userRotation()
|
|
{
|
|
double degrees = mRotationEdit->value();
|
|
mMapCanvas->setRotation( degrees );
|
|
mMapCanvas->refresh();
|
|
}
|
|
|
|
void QgisApp::onFocusChanged( QWidget *oldWidget, QWidget *newWidget )
|
|
{
|
|
Q_UNUSED( oldWidget );
|
|
// If nothing has focus even though this window is active, ensure map canvas receives it
|
|
if ( !newWidget && isActiveWindow() )
|
|
{
|
|
mapCanvas()->setFocus();
|
|
}
|
|
}
|
|
|
|
// toggle overview status
|
|
void QgisApp::isInOverview()
|
|
{
|
|
mLayerTreeView->defaultActions()->showInOverview();
|
|
}
|
|
|
|
void QgisApp::removingLayers( const QStringList &layers )
|
|
{
|
|
Q_FOREACH ( const QString &layerId, layers )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>(
|
|
QgsProject::instance()->mapLayer( layerId ) );
|
|
if ( !vlayer || !vlayer->isEditable() )
|
|
return;
|
|
|
|
toggleEditing( vlayer, false );
|
|
}
|
|
}
|
|
|
|
void QgisApp::removeLayer()
|
|
{
|
|
if ( !mLayerTreeView )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Q_FOREACH ( QgsMapLayer *layer, mLayerTreeView->selectedLayers() )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
|
|
if ( vlayer && vlayer->isEditable() && !toggleEditing( vlayer, true ) )
|
|
return;
|
|
}
|
|
|
|
QStringList activeTaskDescriptions;
|
|
Q_FOREACH ( QgsMapLayer *layer, mLayerTreeView->selectedLayers() )
|
|
{
|
|
QList< QgsTask * > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layer );
|
|
if ( !tasks.isEmpty() )
|
|
{
|
|
Q_FOREACH ( QgsTask *task, tasks )
|
|
{
|
|
activeTaskDescriptions << tr( " • %1" ).arg( task->description() );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !activeTaskDescriptions.isEmpty() )
|
|
{
|
|
QMessageBox::warning( this, tr( "Active tasks" ),
|
|
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( QStringLiteral( "\n" ) ) ) );
|
|
return;
|
|
}
|
|
|
|
QList<QgsLayerTreeNode *> selectedNodes = mLayerTreeView->selectedNodes( true );
|
|
|
|
//validate selection
|
|
if ( selectedNodes.isEmpty() )
|
|
{
|
|
messageBar()->pushMessage( tr( "No legend entries selected" ),
|
|
tr( "Select the layers and groups you want to remove in the legend." ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
bool promptConfirmation = QgsSettings().value( QStringLiteral( "qgis/askToDeleteLayers" ), true ).toBool();
|
|
|
|
// Don't show prompt to remove a empty group.
|
|
if ( selectedNodes.count() == 1
|
|
&& selectedNodes.at( 0 )->nodeType() == QgsLayerTreeNode::NodeGroup
|
|
&& selectedNodes.at( 0 )->children().count() == 0 )
|
|
{
|
|
promptConfirmation = false;
|
|
}
|
|
|
|
bool shiftHeld = QApplication::queryKeyboardModifiers().testFlag( Qt::ShiftModifier );
|
|
//display a warning
|
|
if ( !shiftHeld && promptConfirmation && QMessageBox::warning( this, tr( "Remove layers and groups" ), tr( "Remove %n legend entries?", "number of legend items to remove", selectedNodes.count() ), QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel )
|
|
{
|
|
return;
|
|
}
|
|
|
|
Q_FOREACH ( QgsLayerTreeNode *node, selectedNodes )
|
|
{
|
|
QgsLayerTreeGroup *parentGroup = qobject_cast<QgsLayerTreeGroup *>( node->parent() );
|
|
if ( parentGroup )
|
|
parentGroup->removeChildNode( node );
|
|
}
|
|
|
|
showStatusMessage( tr( "%n legend entries removed.", "number of removed legend entries", selectedNodes.count() ) );
|
|
|
|
refreshMapCanvas();
|
|
}
|
|
|
|
void QgisApp::duplicateLayers( const QList<QgsMapLayer *> &lyrList )
|
|
{
|
|
if ( !mLayerTreeView )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const QList<QgsMapLayer *> selectedLyrs = lyrList.empty() ? mLayerTreeView->selectedLayers() : lyrList;
|
|
if ( selectedLyrs.empty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
freezeCanvases();
|
|
QgsMapLayer *dupLayer = nullptr;
|
|
QString layerDupName, unSppType;
|
|
QList<QgsMessageBarItem *> msgBars;
|
|
|
|
Q_FOREACH ( QgsMapLayer *selectedLyr, selectedLyrs )
|
|
{
|
|
dupLayer = nullptr;
|
|
unSppType.clear();
|
|
layerDupName = selectedLyr->name() + ' ' + tr( "copy" );
|
|
|
|
if ( selectedLyr->type() == QgsMapLayer::PluginLayer )
|
|
{
|
|
unSppType = tr( "Plugin layer" );
|
|
}
|
|
|
|
// duplicate the layer's basic parameters
|
|
|
|
if ( unSppType.isEmpty() )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( selectedLyr );
|
|
// TODO: add other layer types that can be duplicated
|
|
// currently memory and plugin layers are skipped
|
|
if ( vlayer && vlayer->storageType() == QLatin1String( "Memory storage" ) )
|
|
{
|
|
unSppType = tr( "Memory layer" );
|
|
}
|
|
else if ( vlayer )
|
|
{
|
|
dupLayer = vlayer->clone();
|
|
}
|
|
}
|
|
|
|
if ( unSppType.isEmpty() && !dupLayer )
|
|
{
|
|
QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( selectedLyr );
|
|
if ( rlayer )
|
|
{
|
|
dupLayer = rlayer->clone();
|
|
}
|
|
}
|
|
|
|
if ( unSppType.isEmpty() && dupLayer && !dupLayer->isValid() )
|
|
{
|
|
msgBars.append( new QgsMessageBarItem(
|
|
tr( "Duplicate layer: " ),
|
|
tr( "%1 (duplication resulted in invalid layer)" ).arg( selectedLyr->name() ),
|
|
QgsMessageBar::WARNING,
|
|
0,
|
|
mInfoBar ) );
|
|
continue;
|
|
}
|
|
|
|
if ( !unSppType.isEmpty() || !dupLayer )
|
|
{
|
|
msgBars.append( new QgsMessageBarItem(
|
|
tr( "Duplicate layer: " ),
|
|
tr( "%1 (%2 type unsupported)" )
|
|
.arg( selectedLyr->name(),
|
|
!unSppType.isEmpty() ? QStringLiteral( "'" ) + unSppType + "' " : QLatin1String( "" ) ),
|
|
QgsMessageBar::WARNING,
|
|
0,
|
|
mInfoBar ) );
|
|
continue;
|
|
}
|
|
|
|
// add layer to layer registry and legend
|
|
QList<QgsMapLayer *> myList;
|
|
myList << dupLayer;
|
|
QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( false );
|
|
QgsProject::instance()->addMapLayers( myList );
|
|
QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( true );
|
|
|
|
QgsLayerTreeLayer *nodeSelectedLyr = mLayerTreeView->layerTreeModel()->rootGroup()->findLayer( selectedLyr->id() );
|
|
Q_ASSERT( nodeSelectedLyr );
|
|
Q_ASSERT( QgsLayerTree::isGroup( nodeSelectedLyr->parent() ) );
|
|
QgsLayerTreeGroup *parentGroup = QgsLayerTree::toGroup( nodeSelectedLyr->parent() );
|
|
|
|
QgsLayerTreeLayer *nodeDupLayer = parentGroup->insertLayer( parentGroup->children().indexOf( nodeSelectedLyr ) + 1, dupLayer );
|
|
|
|
// always set duplicated layers to not visible so layer can be configured before being turned on
|
|
nodeDupLayer->setItemVisibilityChecked( false );
|
|
|
|
// duplicate the layer style
|
|
QString errMsg;
|
|
QDomDocument style;
|
|
selectedLyr->exportNamedStyle( style, errMsg );
|
|
if ( errMsg.isEmpty() )
|
|
dupLayer->importNamedStyle( style, errMsg );
|
|
if ( !errMsg.isEmpty() )
|
|
messageBar()->pushMessage( errMsg,
|
|
tr( "Cannot copy style to duplicated layer." ),
|
|
QgsMessageBar::CRITICAL, messageTimeout() );
|
|
}
|
|
|
|
dupLayer = nullptr;
|
|
|
|
freezeCanvases( false );
|
|
|
|
// display errors in message bar after duplication of layers
|
|
Q_FOREACH ( QgsMessageBarItem *msgBar, msgBars )
|
|
{
|
|
mInfoBar->pushItem( msgBar );
|
|
}
|
|
}
|
|
|
|
void QgisApp::setLayerScaleVisibility()
|
|
{
|
|
if ( !mLayerTreeView )
|
|
return;
|
|
|
|
QList<QgsMapLayer *> layers = mLayerTreeView->selectedLayers();
|
|
|
|
if ( layers.length() < 1 )
|
|
return;
|
|
|
|
QgsScaleVisibilityDialog *dlg = new QgsScaleVisibilityDialog( this, tr( "Set scale visibility for selected layers" ), mMapCanvas );
|
|
QgsMapLayer *layer = mLayerTreeView->currentLayer();
|
|
if ( layer )
|
|
{
|
|
dlg->setScaleVisiblity( layer->hasScaleBasedVisibility() );
|
|
dlg->setMinimumScale( layer->minimumScale() );
|
|
dlg->setMaximumScale( layer->maximumScale() );
|
|
}
|
|
if ( dlg->exec() )
|
|
{
|
|
freezeCanvases();
|
|
Q_FOREACH ( QgsMapLayer *layer, layers )
|
|
{
|
|
layer->setScaleBasedVisibility( dlg->hasScaleVisibility() );
|
|
layer->setMaximumScale( dlg->maximumScale() );
|
|
layer->setMinimumScale( dlg->minimumScale() );
|
|
}
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
}
|
|
delete dlg;
|
|
}
|
|
|
|
void QgisApp::zoomToLayerScale()
|
|
{
|
|
if ( !mLayerTreeView )
|
|
return;
|
|
|
|
QList<QgsMapLayer *> layers = mLayerTreeView->selectedLayers();
|
|
|
|
if ( layers.length() < 1 )
|
|
return;
|
|
|
|
QgsMapLayer *layer = mLayerTreeView->currentLayer();
|
|
if ( layer && layer->hasScaleBasedVisibility() )
|
|
{
|
|
const double scale = mMapCanvas->scale();
|
|
if ( scale > layer->minimumScale() && layer->minimumScale() > 0 )
|
|
{
|
|
mMapCanvas->zoomScale( layer->minimumScale() * Qgis::SCALE_PRECISION );
|
|
}
|
|
else if ( scale <= layer->maximumScale() && layer->maximumScale() > 0 )
|
|
{
|
|
mMapCanvas->zoomScale( layer->maximumScale() );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::setLayerCrs()
|
|
{
|
|
if ( !( mLayerTreeView && mLayerTreeView->currentLayer() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsProjectionSelectionDialog mySelector( this );
|
|
mySelector.setCrs( mLayerTreeView->currentLayer()->crs() );
|
|
mySelector.setMessage( QString() );
|
|
if ( !mySelector.exec() )
|
|
{
|
|
QApplication::restoreOverrideCursor();
|
|
return;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem crs = mySelector.crs();
|
|
|
|
Q_FOREACH ( QgsLayerTreeNode *node, mLayerTreeView->selectedNodes() )
|
|
{
|
|
if ( QgsLayerTree::isGroup( node ) )
|
|
{
|
|
Q_FOREACH ( QgsLayerTreeLayer *child, QgsLayerTree::toGroup( node )->findLayers() )
|
|
{
|
|
if ( child->layer() )
|
|
{
|
|
child->layer()->setCrs( crs );
|
|
child->layer()->triggerRepaint();
|
|
}
|
|
}
|
|
}
|
|
else if ( QgsLayerTree::isLayer( node ) )
|
|
{
|
|
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
|
|
if ( nodeLayer->layer() )
|
|
{
|
|
nodeLayer->layer()->setCrs( crs );
|
|
nodeLayer->layer()->triggerRepaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
refreshMapCanvas();
|
|
}
|
|
|
|
void QgisApp::setProjectCrsFromLayer()
|
|
{
|
|
if ( !( mLayerTreeView && mLayerTreeView->currentLayer() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem crs = mLayerTreeView->currentLayer()->crs();
|
|
mMapCanvas->freeze();
|
|
QgsProject::instance()->setCrs( crs );
|
|
mMapCanvas->freeze( false );
|
|
mMapCanvas->refresh();
|
|
}
|
|
|
|
|
|
void QgisApp::legendLayerZoomNative()
|
|
{
|
|
if ( !mLayerTreeView )
|
|
return;
|
|
|
|
//find current Layer
|
|
QgsMapLayer *currentLayer = mLayerTreeView->currentLayer();
|
|
if ( !currentLayer )
|
|
return;
|
|
|
|
QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( currentLayer );
|
|
if ( layer )
|
|
{
|
|
QgsDebugMsg( "Raster units per pixel : " + QString::number( layer->rasterUnitsPerPixelX() ) );
|
|
QgsDebugMsg( "MapUnitsPerPixel before : " + QString::number( mMapCanvas->mapUnitsPerPixel() ) );
|
|
|
|
// get length of central canvas pixel width in source raster crs
|
|
QgsRectangle e = mMapCanvas->extent();
|
|
QSize s = mMapCanvas->mapSettings().outputSize();
|
|
QgsPointXY p1( e.center().x(), e.center().y() );
|
|
QgsPointXY p2( e.center().x() + e.width() / s.width(), e.center().y() + e.height() / s.height() );
|
|
QgsCoordinateTransform ct( mMapCanvas->mapSettings().destinationCrs(), layer->crs() );
|
|
p1 = ct.transform( p1 );
|
|
p2 = ct.transform( p2 );
|
|
double width = std::sqrt( p1.sqrDist( p2 ) ); // width (actually the diagonal) of reprojected pixel
|
|
mMapCanvas->zoomByFactor( std::sqrt( layer->rasterUnitsPerPixelX() * layer->rasterUnitsPerPixelX() + layer->rasterUnitsPerPixelY() * layer->rasterUnitsPerPixelY() ) / width );
|
|
|
|
mMapCanvas->refresh();
|
|
QgsDebugMsg( "MapUnitsPerPixel after : " + QString::number( mMapCanvas->mapUnitsPerPixel() ) );
|
|
}
|
|
}
|
|
|
|
void QgisApp::legendLayerStretchUsingCurrentExtent()
|
|
{
|
|
if ( !mLayerTreeView )
|
|
return;
|
|
|
|
//find current Layer
|
|
QgsMapLayer *currentLayer = mLayerTreeView->currentLayer();
|
|
if ( !currentLayer )
|
|
return;
|
|
|
|
QgsRasterLayer *layer = qobject_cast<QgsRasterLayer *>( currentLayer );
|
|
if ( layer )
|
|
{
|
|
QgsRectangle myRectangle;
|
|
myRectangle = mMapCanvas->mapSettings().outputExtentToLayerExtent( layer, mMapCanvas->extent() );
|
|
layer->refreshContrastEnhancement( myRectangle );
|
|
|
|
mLayerTreeView->refreshLayerSymbology( layer->id() );
|
|
refreshMapCanvas();
|
|
}
|
|
}
|
|
|
|
void QgisApp::applyStyleToGroup()
|
|
{
|
|
if ( !mLayerTreeView )
|
|
return;
|
|
|
|
Q_FOREACH ( QgsLayerTreeNode *node, mLayerTreeView->selectedNodes() )
|
|
{
|
|
if ( QgsLayerTree::isGroup( node ) )
|
|
{
|
|
Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, QgsLayerTree::toGroup( node )->findLayers() )
|
|
{
|
|
if ( nodeLayer->layer() )
|
|
{
|
|
pasteStyle( nodeLayer->layer() );
|
|
}
|
|
}
|
|
}
|
|
else if ( QgsLayerTree::isLayer( node ) )
|
|
{
|
|
QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
|
|
if ( nodeLayer->layer() )
|
|
{
|
|
pasteStyle( nodeLayer->layer() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::legendGroupSetCrs()
|
|
{
|
|
if ( !mMapCanvas )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsLayerTreeGroup *currentGroup = mLayerTreeView->currentGroupNode();
|
|
if ( !currentGroup )
|
|
return;
|
|
|
|
QgsProjectionSelectionDialog mySelector( this );
|
|
mySelector.setMessage( QString() );
|
|
if ( !mySelector.exec() )
|
|
{
|
|
QApplication::restoreOverrideCursor();
|
|
return;
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem crs = mySelector.crs();
|
|
Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, currentGroup->findLayers() )
|
|
{
|
|
if ( nodeLayer->layer() )
|
|
{
|
|
nodeLayer->layer()->setCrs( crs );
|
|
nodeLayer->layer()->triggerRepaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::legendGroupSetWmsData()
|
|
{
|
|
QgsLayerTreeGroup *currentGroup = mLayerTreeView->currentGroupNode();
|
|
if ( !currentGroup )
|
|
return;
|
|
QgsGroupWmsDataDialog *dlg = new QgsGroupWmsDataDialog( this );
|
|
dlg->setGroupShortName( currentGroup->customProperty( QStringLiteral( "wmsShortName" ) ).toString() );
|
|
dlg->setGroupTitle( currentGroup->customProperty( QStringLiteral( "wmsTitle" ) ).toString() );
|
|
dlg->setGroupTitle( currentGroup->customProperty( QStringLiteral( "wmsAbstract" ) ).toString() );
|
|
if ( dlg->exec() )
|
|
{
|
|
currentGroup->setCustomProperty( QStringLiteral( "wmsShortName" ), dlg->groupShortName() );
|
|
currentGroup->setCustomProperty( QStringLiteral( "wmsTitle" ), dlg->groupTitle() );
|
|
currentGroup->setCustomProperty( QStringLiteral( "wmsAbstract" ), dlg->groupAbstract() );
|
|
}
|
|
delete dlg;
|
|
}
|
|
|
|
void QgisApp::zoomToLayerExtent()
|
|
{
|
|
mLayerTreeView->defaultActions()->zoomToLayer( mMapCanvas );
|
|
}
|
|
|
|
void QgisApp::showPluginManager()
|
|
{
|
|
#ifdef WITH_BINDINGS
|
|
if ( mPythonUtils && mPythonUtils->isEnabled() )
|
|
{
|
|
// Call pluginManagerInterface()->showPluginManager() as soon as the plugin installer says the remote data is fetched.
|
|
QgsPythonRunner::run( QStringLiteral( "pyplugin_installer.instance().showPluginManagerWhenReady()" ) );
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// Call the pluginManagerInterface directly
|
|
mQgisInterface->pluginManagerInterface()->showPluginManager();
|
|
}
|
|
}
|
|
|
|
void QgisApp::installPluginFromZip()
|
|
{
|
|
#ifdef WITH_BINDINGS
|
|
if ( mPythonUtils && mPythonUtils->isEnabled() )
|
|
{
|
|
QgsPythonRunner::run( QStringLiteral( "pyplugin_installer.instance().installFromZipFile()" ) );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
// implementation of the python runner
|
|
class QgsPythonRunnerImpl : public QgsPythonRunner
|
|
{
|
|
public:
|
|
explicit QgsPythonRunnerImpl( QgsPythonUtils *pythonUtils ) : mPythonUtils( pythonUtils ) {}
|
|
|
|
bool runCommand( QString command, QString messageOnError = QString() ) override
|
|
{
|
|
#ifdef WITH_BINDINGS
|
|
if ( mPythonUtils && mPythonUtils->isEnabled() )
|
|
{
|
|
return mPythonUtils->runString( command, messageOnError, false );
|
|
}
|
|
#else
|
|
Q_UNUSED( command );
|
|
Q_UNUSED( messageOnError );
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool evalCommand( QString command, QString &result ) override
|
|
{
|
|
#ifdef WITH_BINDINGS
|
|
if ( mPythonUtils && mPythonUtils->isEnabled() )
|
|
{
|
|
return mPythonUtils->evalString( command, result );
|
|
}
|
|
#else
|
|
Q_UNUSED( command );
|
|
Q_UNUSED( result );
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
protected:
|
|
QgsPythonUtils *mPythonUtils = nullptr;
|
|
};
|
|
|
|
void QgisApp::loadPythonSupport()
|
|
{
|
|
QString pythonlibName( QStringLiteral( "qgispython" ) );
|
|
#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
|
|
pythonlibName.prepend( QgsApplication::libraryPath() );
|
|
#endif
|
|
#ifdef __MINGW32__
|
|
pythonlibName.prepend( "lib" );
|
|
#endif
|
|
QString version = QStringLiteral( "%1.%2.%3" ).arg( Qgis::QGIS_VERSION_INT / 10000 ).arg( Qgis::QGIS_VERSION_INT / 100 % 100 ).arg( Qgis::QGIS_VERSION_INT % 100 );
|
|
QgsDebugMsg( QString( "load library %1 (%2)" ).arg( pythonlibName, version ) );
|
|
QLibrary pythonlib( pythonlibName, version );
|
|
// It's necessary to set these two load hints, otherwise Python library won't work correctly
|
|
// see http://lists.kde.org/?l=pykde&m=117190116820758&w=2
|
|
pythonlib.setLoadHints( QLibrary::ResolveAllSymbolsHint | QLibrary::ExportExternalSymbolsHint );
|
|
if ( !pythonlib.load() )
|
|
{
|
|
pythonlib.setFileName( pythonlibName );
|
|
if ( !pythonlib.load() )
|
|
{
|
|
QgsMessageLog::logMessage( tr( "Couldn't load Python support library: %1" ).arg( pythonlib.errorString() ) );
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_BINDINGS
|
|
|
|
//QgsDebugMsg("Python support library loaded successfully.");
|
|
typedef QgsPythonUtils*( *inst )();
|
|
inst pythonlib_inst = reinterpret_cast< inst >( cast_to_fptr( pythonlib.resolve( "instance" ) ) );
|
|
if ( !pythonlib_inst )
|
|
{
|
|
//using stderr on purpose because we want end users to see this [TS]
|
|
QgsMessageLog::logMessage( tr( "Couldn't resolve python support library's instance() symbol." ) );
|
|
return;
|
|
}
|
|
|
|
//QgsDebugMsg("Python support library's instance() symbol resolved.");
|
|
mPythonUtils = pythonlib_inst();
|
|
if ( mPythonUtils )
|
|
{
|
|
mPythonUtils->initPython( mQgisInterface );
|
|
}
|
|
|
|
if ( mPythonUtils && mPythonUtils->isEnabled() )
|
|
{
|
|
QgsPluginRegistry::instance()->setPythonUtils( mPythonUtils );
|
|
|
|
// init python runner
|
|
QgsPythonRunner::setInstance( new QgsPythonRunnerImpl( mPythonUtils ) );
|
|
|
|
QgsMessageLog::logMessage( tr( "Python support ENABLED :-) " ), QString(), QgsMessageLog::INFO );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::checkQgisVersion()
|
|
{
|
|
QgsVersionInfo *versionInfo = new QgsVersionInfo();
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
|
|
connect( versionInfo, &QgsVersionInfo::versionInfoAvailable, this, &QgisApp::versionReplyFinished );
|
|
versionInfo->checkVersion();
|
|
}
|
|
|
|
void QgisApp::versionReplyFinished()
|
|
{
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
QgsVersionInfo *versionInfo = qobject_cast<QgsVersionInfo *>( sender() );
|
|
Q_ASSERT( versionInfo );
|
|
|
|
if ( versionInfo->error() == QNetworkReply::NoError )
|
|
{
|
|
QString info;
|
|
|
|
if ( versionInfo->newVersionAvailable() )
|
|
{
|
|
info = tr( "There is a new version of QGIS available" );
|
|
}
|
|
else if ( versionInfo->isDevelopmentVersion() )
|
|
{
|
|
info = tr( "You are running a development version of QGIS" );
|
|
}
|
|
else
|
|
{
|
|
info = tr( "You are running the current version of QGIS" );
|
|
}
|
|
|
|
info = QStringLiteral( "<b>%1</b>" ).arg( info );
|
|
|
|
info += "<br>" + versionInfo->downloadInfo();
|
|
|
|
QMessageBox mb( QMessageBox::Information, tr( "QGIS Version Information" ), info );
|
|
mb.setInformativeText( versionInfo->html() );
|
|
mb.exec();
|
|
}
|
|
else
|
|
{
|
|
QMessageBox mb( QMessageBox::Warning, tr( "QGIS Version Information" ), tr( "Unable to get current version information from server" ) );
|
|
mb.setDetailedText( versionInfo->errorString() );
|
|
mb.exec();
|
|
}
|
|
}
|
|
|
|
void QgisApp::configureShortcuts()
|
|
{
|
|
QgsConfigureShortcutsDialog dlg( this );
|
|
dlg.exec();
|
|
}
|
|
|
|
void QgisApp::customize()
|
|
{
|
|
QgsCustomization::instance()->openDialog( this );
|
|
}
|
|
|
|
void QgisApp::options()
|
|
{
|
|
showOptionsDialog( this );
|
|
}
|
|
|
|
void QgisApp::showOptionsDialog( QWidget *parent, const QString ¤tPage )
|
|
{
|
|
QgsSettings mySettings;
|
|
QString oldScales = mySettings.value( QStringLiteral( "Map/scales" ), PROJECT_SCALES ).toString();
|
|
|
|
bool oldCapitalize = mySettings.value( QStringLiteral( "qgis/capitalizeLayerName" ), QVariant( false ) ).toBool();
|
|
|
|
QList< QgsOptionsWidgetFactory * > factories;
|
|
Q_FOREACH ( const QPointer< QgsOptionsWidgetFactory > &f, mOptionsWidgetFactories )
|
|
{
|
|
// remove any deleted factories
|
|
if ( f )
|
|
factories << f;
|
|
}
|
|
std::unique_ptr< QgsOptions > optionsDialog( new QgsOptions( parent, QgsGuiUtils::ModalDialogFlags, factories ) );
|
|
if ( !currentPage.isEmpty() )
|
|
{
|
|
optionsDialog->setCurrentPage( currentPage );
|
|
}
|
|
|
|
if ( optionsDialog->exec() )
|
|
{
|
|
QgsProject::instance()->layerTreeRegistryBridge()->setNewLayersVisible( mySettings.value( QStringLiteral( "qgis/new_layers_visible" ), true ).toBool() );
|
|
|
|
setupLayerTreeViewFromSettings();
|
|
|
|
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
|
|
{
|
|
applyDefaultSettingsToCanvas( canvas );
|
|
}
|
|
|
|
if ( oldCapitalize != mySettings.value( QStringLiteral( "qgis/capitalizeLayerName" ), QVariant( false ) ).toBool() )
|
|
{
|
|
// if the layer capitalization has changed, we need to update all layer names
|
|
Q_FOREACH ( QgsMapLayer *layer, QgsProject::instance()->mapLayers() )
|
|
layer->setName( layer->originalName() );
|
|
}
|
|
|
|
//update any open compositions so they reflect new composer settings
|
|
//we have to push the changes to the compositions here, because compositions
|
|
//have no access to qgisapp and accordingly can't listen in to changes
|
|
Q_FOREACH ( QgsComposition *c, QgsProject::instance()->layoutManager()->compositions() )
|
|
{
|
|
c->updateSettings();
|
|
}
|
|
|
|
//do we need this? TS
|
|
Q_FOREACH ( QgsMapCanvas *canvas, mapCanvases() )
|
|
{
|
|
canvas->refresh();
|
|
}
|
|
|
|
mRasterFileFilter = QgsProviderRegistry::instance()->fileRasterFilters();
|
|
|
|
if ( oldScales != mySettings.value( QStringLiteral( "Map/scales" ), PROJECT_SCALES ).toString() )
|
|
{
|
|
mScaleWidget->updateScales();
|
|
}
|
|
|
|
qobject_cast<QgsMeasureTool *>( mMapTools.mMeasureDist )->updateSettings();
|
|
qobject_cast<QgsMeasureTool *>( mMapTools.mMeasureArea )->updateSettings();
|
|
qobject_cast<QgsMapToolMeasureAngle *>( mMapTools.mMeasureAngle )->updateSettings();
|
|
|
|
double factor = mySettings.value( QStringLiteral( "qgis/magnifier_factor_default" ), 1.0 ).toDouble();
|
|
mMagnifierWidget->setDefaultFactor( factor );
|
|
mMagnifierWidget->updateMagnification( factor );
|
|
}
|
|
}
|
|
|
|
void QgisApp::fullHistogramStretch()
|
|
{
|
|
histogramStretch( false, QgsRasterMinMaxOrigin::MinMax );
|
|
}
|
|
|
|
void QgisApp::localHistogramStretch()
|
|
{
|
|
histogramStretch( true, QgsRasterMinMaxOrigin::MinMax );
|
|
}
|
|
|
|
void QgisApp::fullCumulativeCutStretch()
|
|
{
|
|
histogramStretch( false, QgsRasterMinMaxOrigin::CumulativeCut );
|
|
}
|
|
|
|
void QgisApp::localCumulativeCutStretch()
|
|
{
|
|
histogramStretch( true, QgsRasterMinMaxOrigin::CumulativeCut );
|
|
}
|
|
|
|
void QgisApp::histogramStretch( bool visibleAreaOnly, QgsRasterMinMaxOrigin::Limits limits )
|
|
{
|
|
QgsMapLayer *myLayer = mLayerTreeView->currentLayer();
|
|
|
|
if ( !myLayer )
|
|
{
|
|
messageBar()->pushMessage( tr( "No Layer Selected" ),
|
|
tr( "To perform a full histogram stretch, you need to have a raster layer selected." ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
QgsRasterLayer *myRasterLayer = qobject_cast<QgsRasterLayer *>( myLayer );
|
|
if ( !myRasterLayer )
|
|
{
|
|
messageBar()->pushMessage( tr( "No Layer Selected" ),
|
|
tr( "To perform a full histogram stretch, you need to have a raster layer selected." ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
QgsRectangle myRectangle;
|
|
if ( visibleAreaOnly )
|
|
myRectangle = mMapCanvas->mapSettings().outputExtentToLayerExtent( myRasterLayer, mMapCanvas->extent() );
|
|
|
|
myRasterLayer->setContrastEnhancement( QgsContrastEnhancement::StretchToMinimumMaximum, limits, myRectangle );
|
|
|
|
myRasterLayer->triggerRepaint();
|
|
}
|
|
|
|
void QgisApp::increaseBrightness()
|
|
{
|
|
int step = 1;
|
|
if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
|
|
{
|
|
step = 10;
|
|
}
|
|
adjustBrightnessContrast( step );
|
|
}
|
|
|
|
void QgisApp::decreaseBrightness()
|
|
{
|
|
int step = -1;
|
|
if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
|
|
{
|
|
step = -10;
|
|
}
|
|
adjustBrightnessContrast( step );
|
|
}
|
|
|
|
void QgisApp::increaseContrast()
|
|
{
|
|
int step = 1;
|
|
if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
|
|
{
|
|
step = 10;
|
|
}
|
|
adjustBrightnessContrast( step, false );
|
|
}
|
|
|
|
void QgisApp::decreaseContrast()
|
|
{
|
|
int step = -1;
|
|
if ( QgsApplication::keyboardModifiers() == Qt::ShiftModifier )
|
|
{
|
|
step = -10;
|
|
}
|
|
adjustBrightnessContrast( step, false );
|
|
}
|
|
|
|
void QgisApp::adjustBrightnessContrast( int delta, bool updateBrightness )
|
|
{
|
|
Q_FOREACH ( QgsMapLayer *layer, mLayerTreeView->selectedLayers() )
|
|
{
|
|
if ( !layer )
|
|
{
|
|
messageBar()->pushMessage( tr( "No Layer Selected" ),
|
|
tr( "To change brightness or contrast, you need to have a raster layer selected." ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer );
|
|
if ( !rasterLayer )
|
|
{
|
|
messageBar()->pushMessage( tr( "No Layer Selected" ),
|
|
tr( "To change brightness or contrast, you need to have a raster layer selected." ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
return;
|
|
}
|
|
|
|
QgsBrightnessContrastFilter *brightnessFilter = rasterLayer->brightnessFilter();
|
|
|
|
if ( updateBrightness )
|
|
{
|
|
brightnessFilter->setBrightness( brightnessFilter->brightness() + delta );
|
|
}
|
|
else
|
|
{
|
|
brightnessFilter->setContrast( brightnessFilter->contrast() + delta );
|
|
}
|
|
|
|
rasterLayer->triggerRepaint();
|
|
}
|
|
}
|
|
|
|
|
|
void QgisApp::helpContents()
|
|
{
|
|
QgsHelp::openHelp( QStringLiteral( "index.html" ) );
|
|
}
|
|
|
|
void QgisApp::apiDocumentation()
|
|
{
|
|
if ( QFileInfo::exists( QgsApplication::pkgDataPath() + "/doc/api/index.html" ) )
|
|
{
|
|
openURL( QStringLiteral( "api/index.html" ) );
|
|
}
|
|
else
|
|
{
|
|
QgsSettings settings;
|
|
QString QgisApiUrl = settings.value( QStringLiteral( "qgis/QgisApiUrl" ),
|
|
QStringLiteral( "https://qgis.org/api/" ) ).toString();
|
|
openURL( QgisApiUrl, false );
|
|
}
|
|
}
|
|
|
|
void QgisApp::reportaBug()
|
|
{
|
|
QgsSettings settings;
|
|
QString reportaBugUrl = settings.value( QStringLiteral( "qgis/reportaBugUrl" ),
|
|
tr( "https://qgis.org/en/site/getinvolved/development/bugreporting.html" ) ).toString();
|
|
openURL( reportaBugUrl, false );
|
|
}
|
|
|
|
void QgisApp::supportProviders()
|
|
{
|
|
QgsSettings settings;
|
|
QString supportProvidersUrl = settings.value( QStringLiteral( "qgis/supportProvidersUrl" ),
|
|
tr( "https://qgis.org/en/site/forusers/commercial_support.html" ) ).toString();
|
|
openURL( supportProvidersUrl, false );
|
|
}
|
|
|
|
void QgisApp::helpQgisHomePage()
|
|
{
|
|
QgsSettings settings;
|
|
QString helpQgisHomePageUrl = settings.value( QStringLiteral( "qgis/helpQgisHomePageUrl" ),
|
|
QStringLiteral( "https://qgis.org" ) ).toString();
|
|
openURL( helpQgisHomePageUrl, false );
|
|
}
|
|
|
|
void QgisApp::openURL( QString url, bool useQgisDocDirectory )
|
|
{
|
|
// open help in user browser
|
|
if ( useQgisDocDirectory )
|
|
{
|
|
url = "file://" + QgsApplication::pkgDataPath() + "/doc/" + url;
|
|
}
|
|
#ifdef Q_OS_MACX
|
|
/* Use Mac OS X Launch Services which uses the user's default browser
|
|
* and will just open a new window if that browser is already running.
|
|
* QProcess creates a new browser process for each invocation and expects a
|
|
* commandline application rather than a bundled application.
|
|
*/
|
|
CFURLRef urlRef = CFURLCreateWithBytes( kCFAllocatorDefault,
|
|
reinterpret_cast<const UInt8 *>( url.toUtf8().data() ), url.length(),
|
|
kCFStringEncodingUTF8, nullptr );
|
|
OSStatus status = LSOpenCFURLRef( urlRef, nullptr );
|
|
status = 0; //avoid compiler warning
|
|
CFRelease( urlRef );
|
|
#elif defined(Q_OS_WIN)
|
|
if ( url.startsWith( "file://", Qt::CaseInsensitive ) )
|
|
ShellExecute( 0, 0, url.mid( 7 ).toLocal8Bit().constData(), 0, 0, SW_SHOWNORMAL );
|
|
else
|
|
QDesktopServices::openUrl( url );
|
|
#else
|
|
QDesktopServices::openUrl( url );
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::registerMapLayerPropertiesFactory( QgsMapLayerConfigWidgetFactory *factory )
|
|
{
|
|
mMapLayerPanelFactories << factory;
|
|
if ( mMapStyleWidget )
|
|
mMapStyleWidget->setPageFactories( mMapLayerPanelFactories );
|
|
}
|
|
|
|
void QgisApp::unregisterMapLayerPropertiesFactory( QgsMapLayerConfigWidgetFactory *factory )
|
|
{
|
|
mMapLayerPanelFactories.removeAll( factory );
|
|
if ( mMapStyleWidget )
|
|
mMapStyleWidget->setPageFactories( mMapLayerPanelFactories );
|
|
}
|
|
|
|
void QgisApp::registerOptionsWidgetFactory( QgsOptionsWidgetFactory *factory )
|
|
{
|
|
mOptionsWidgetFactories << factory;
|
|
}
|
|
|
|
void QgisApp::unregisterOptionsWidgetFactory( QgsOptionsWidgetFactory *factory )
|
|
{
|
|
mOptionsWidgetFactories.removeAll( factory );
|
|
}
|
|
|
|
QgsMapLayer *QgisApp::activeLayer()
|
|
{
|
|
return mLayerTreeView ? mLayerTreeView->currentLayer() : nullptr;
|
|
}
|
|
|
|
QSize QgisApp::iconSize( bool dockedToolbar ) const
|
|
{
|
|
QgsSettings s;
|
|
int size = s.value( QStringLiteral( "/IconSize" ), 32 ).toInt();
|
|
|
|
if ( dockedToolbar )
|
|
{
|
|
size = dockedToolbarIconSize( size );
|
|
}
|
|
|
|
return QSize( size, size );
|
|
}
|
|
|
|
bool QgisApp::setActiveLayer( QgsMapLayer *layer )
|
|
{
|
|
if ( !layer )
|
|
return false;
|
|
|
|
if ( !mLayerTreeView->layerTreeModel()->rootGroup()->findLayer( layer->id() ) )
|
|
return false;
|
|
|
|
mLayerTreeView->setCurrentLayer( layer );
|
|
return true;
|
|
}
|
|
|
|
void QgisApp::reloadConnections()
|
|
{
|
|
emit connectionsChanged( );
|
|
}
|
|
|
|
|
|
QgsVectorLayer *QgisApp::addVectorLayer( const QString &vectorLayerPath, const QString &baseName, const QString &providerKey )
|
|
{
|
|
bool wasfrozen = mMapCanvas->isFrozen();
|
|
|
|
freezeCanvases();
|
|
|
|
// Let render() do its own cursor management
|
|
// QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
/* Eliminate the need to instantiate the layer based on provider type.
|
|
The caller is responsible for cobbling together the needed information to
|
|
open the layer
|
|
*/
|
|
QgsDebugMsg( "Creating new vector layer using " + vectorLayerPath
|
|
+ " with baseName of " + baseName
|
|
+ " and providerKey of " + providerKey );
|
|
|
|
// if the layer needs authentication, ensure the master password is set
|
|
bool authok = true;
|
|
QRegExp rx( "authcfg=([a-z]|[A-Z]|[0-9]){7}" );
|
|
if ( rx.indexIn( vectorLayerPath ) != -1 )
|
|
{
|
|
authok = false;
|
|
if ( !QgsAuthGuiUtils::isDisabled( messageBar(), messageTimeout() ) )
|
|
{
|
|
authok = QgsAuthManager::instance()->setMasterPassword( true );
|
|
}
|
|
}
|
|
|
|
// create the layer
|
|
QgsVectorLayer *layer = new QgsVectorLayer( vectorLayerPath, baseName, providerKey, false );
|
|
|
|
if ( authok && layer && layer->isValid() )
|
|
{
|
|
QStringList sublayers = layer->dataProvider()->subLayers();
|
|
QgsDebugMsg( QString( "got valid layer with %1 sublayers" ).arg( sublayers.count() ) );
|
|
|
|
// If the newly created layer has more than 1 layer of data available, we show the
|
|
// sublayers selection dialog so the user can select the sublayers to actually load.
|
|
if ( sublayers.count() > 1 && ! vectorLayerPath.contains( QStringLiteral( "layerid=" ) ) )
|
|
{
|
|
askUserForOGRSublayers( layer );
|
|
|
|
// The first layer loaded is not useful in that case. The user can select it in
|
|
// the list if he wants to load it.
|
|
delete layer;
|
|
layer = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// Register this layer with the layers registry
|
|
QList<QgsMapLayer *> myList;
|
|
|
|
//set friendly name for datasources with only one layer
|
|
QStringList sublayers = layer->dataProvider()->subLayers();
|
|
if ( !sublayers.isEmpty() )
|
|
{
|
|
QStringList elements = sublayers.at( 0 ).split( ':' );
|
|
|
|
if ( elements.size() >= 4 && layer->name() != elements.at( 1 ) )
|
|
{
|
|
layer->setName( QStringLiteral( "%1 %2 %3" ).arg( layer->name(), elements.at( 1 ), elements.at( 3 ) ) );
|
|
}
|
|
}
|
|
|
|
myList << layer;
|
|
QgsProject::instance()->addMapLayers( myList );
|
|
bool ok;
|
|
layer->loadDefaultStyle( ok );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QString msg = tr( "The layer %1 is not a valid layer and can not be added to the map" ).arg( vectorLayerPath );
|
|
messageBar()->pushMessage( tr( "Layer is not valid" ), msg, QgsMessageBar::CRITICAL, messageTimeout() );
|
|
|
|
delete layer;
|
|
freezeCanvases( false );
|
|
return nullptr;
|
|
}
|
|
|
|
// Only update the map if we frozen in this method
|
|
// Let the caller do it otherwise
|
|
if ( !wasfrozen )
|
|
{
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
}
|
|
|
|
// Let render() do its own cursor management
|
|
// QApplication::restoreOverrideCursor();
|
|
|
|
return layer;
|
|
|
|
} // QgisApp::addVectorLayer
|
|
|
|
|
|
|
|
void QgisApp::addMapLayer( QgsMapLayer *mapLayer )
|
|
{
|
|
freezeCanvases();
|
|
|
|
// Let render() do its own cursor management
|
|
// QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
if ( mapLayer->isValid() )
|
|
{
|
|
// Register this layer with the layers registry
|
|
QList<QgsMapLayer *> myList;
|
|
myList << mapLayer;
|
|
QgsProject::instance()->addMapLayers( myList );
|
|
// add it to the mapcanvas collection
|
|
// not necessary since adding to registry adds to canvas mMapCanvas->addLayer(theMapLayer);
|
|
}
|
|
else
|
|
{
|
|
QString msg = tr( "The layer is not a valid layer and can not be added to the map" );
|
|
messageBar()->pushMessage( tr( "Layer is not valid" ), msg, QgsMessageBar::CRITICAL, messageTimeout() );
|
|
}
|
|
|
|
// draw the map
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
|
|
// Let render() do its own cursor management
|
|
// QApplication::restoreOverrideCursor();
|
|
|
|
}
|
|
|
|
void QgisApp::embedLayers()
|
|
{
|
|
//dialog to select groups/layers from other project files
|
|
QgsProjectLayerGroupDialog d( this );
|
|
if ( d.exec() == QDialog::Accepted && d.isValid() )
|
|
{
|
|
freezeCanvases();
|
|
|
|
QString projectFile = d.selectedProjectFile();
|
|
|
|
//groups
|
|
QStringList groups = d.selectedGroups();
|
|
QStringList::const_iterator groupIt = groups.constBegin();
|
|
for ( ; groupIt != groups.constEnd(); ++groupIt )
|
|
{
|
|
QgsLayerTreeGroup *newGroup = QgsProject::instance()->createEmbeddedGroup( *groupIt, projectFile, QStringList() );
|
|
|
|
if ( newGroup )
|
|
QgsProject::instance()->layerTreeRoot()->addChildNode( newGroup );
|
|
}
|
|
|
|
//layer ids
|
|
QList<QDomNode> brokenNodes;
|
|
|
|
// resolve dependencies
|
|
QgsLayerDefinition::DependencySorter depSorter( projectFile );
|
|
QStringList sortedIds = depSorter.sortedLayerIds();
|
|
QStringList layerIds = d.selectedLayerIds();
|
|
Q_FOREACH ( const QString &id, sortedIds )
|
|
{
|
|
Q_FOREACH ( const QString &selId, layerIds )
|
|
{
|
|
if ( selId == id )
|
|
QgsProject::instance()->createEmbeddedLayer( selId, projectFile, brokenNodes );
|
|
}
|
|
}
|
|
|
|
freezeCanvases( false );
|
|
if ( !groups.isEmpty() || !layerIds.isEmpty() )
|
|
{
|
|
refreshMapCanvas();
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::newMapCanvas()
|
|
{
|
|
int i = 1;
|
|
|
|
bool existing = true;
|
|
QList< QgsMapCanvas * > existingCanvases = mapCanvases();
|
|
QString name;
|
|
while ( existing )
|
|
{
|
|
name = tr( "Map %1" ).arg( i++ );
|
|
existing = false;
|
|
Q_FOREACH ( QgsMapCanvas *canvas, existingCanvases )
|
|
{
|
|
if ( canvas->objectName() == name )
|
|
{
|
|
existing = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsMapCanvasDockWidget *dock = createNewMapCanvasDock( name, true );
|
|
if ( dock )
|
|
{
|
|
dock->mapCanvas()->setLayers( mMapCanvas->layers() );
|
|
dock->mapCanvas()->setExtent( mMapCanvas->extent() );
|
|
QgsDebugMsgLevel( QString( "QgisApp::newMapCanvas() -4- : QgsProject::instance()->crs().description[%1] ellipsoid[%2]" ).arg( QgsProject::instance()->crs().description() ).arg( QgsProject::instance()->crs().ellipsoidAcronym() ), 3 );
|
|
dock->mapCanvas()->setDestinationCrs( QgsProject::instance()->crs() );
|
|
dock->mapCanvas()->freeze( false );
|
|
}
|
|
}
|
|
|
|
void QgisApp::init3D()
|
|
{
|
|
#ifdef HAVE_3D
|
|
// register 3D renderers
|
|
QgsApplication::instance()->renderer3DRegistry()->addRenderer( new QgsVectorLayer3DRendererMetadata );
|
|
#else
|
|
mActionNew3DMapCanvas->setVisible( false );
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::new3DMapCanvas()
|
|
{
|
|
#ifdef HAVE_3D
|
|
|
|
// initialize from project
|
|
QgsProject *prj = QgsProject::instance();
|
|
QgsRectangle fullExtent = mMapCanvas->fullExtent();
|
|
|
|
// some layers may go crazy and make full extent unusable
|
|
// we can't go any further - invalid extent would break everything
|
|
if ( fullExtent.isEmpty() || !fullExtent.isFinite() )
|
|
{
|
|
QMessageBox::warning( this, tr( "Error" ), tr( "Project extent is not valid." ) );
|
|
return;
|
|
}
|
|
|
|
Qgs3DMapSettings *map = new Qgs3DMapSettings;
|
|
map->setCrs( prj->crs() );
|
|
map->setOrigin( fullExtent.center().x(), fullExtent.center().y(), 0 );
|
|
map->setSelectionColor( mMapCanvas->selectionColor() );
|
|
map->setBackgroundColor( mMapCanvas->canvasColor() );
|
|
map->setLayers( mMapCanvas->layers() );
|
|
|
|
QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator;
|
|
flatTerrain->setCrs( map->crs() );
|
|
flatTerrain->setExtent( fullExtent );
|
|
map->setTerrainGenerator( flatTerrain );
|
|
|
|
// TODO: combine with code in createNewMapCanvasDock()
|
|
Qgs3DMapCanvasDockWidget *map3DWidget = new Qgs3DMapCanvasDockWidget( this );
|
|
map3DWidget->setWindowTitle( "3D Map" );
|
|
map3DWidget->setAllowedAreas( Qt::AllDockWidgetAreas );
|
|
map3DWidget->setGeometry( QRect( rect().width() * 0.75, rect().height() * 0.5, 400, 400 ) );
|
|
map3DWidget->setMap( map );
|
|
map3DWidget->setMainCanvas( mMapCanvas );
|
|
addDockWidget( Qt::BottomDockWidgetArea, map3DWidget );
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::setExtent( const QgsRectangle &rect )
|
|
{
|
|
mMapCanvas->setExtent( rect );
|
|
}
|
|
|
|
/**
|
|
Prompt and save if project has been modified.
|
|
@return true if saved or discarded, false if canceled
|
|
*/
|
|
bool QgisApp::saveDirty()
|
|
{
|
|
QString whyDirty;
|
|
bool hasUnsavedEdits = false;
|
|
// extra check to see if there are any vector layers with unsaved provider edits
|
|
// to ensure user has opportunity to save any editing
|
|
if ( QgsProject::instance()->count() > 0 )
|
|
{
|
|
QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
|
|
for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
|
|
{
|
|
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
|
|
if ( !vl )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
hasUnsavedEdits = ( vl->isEditable() && vl->isModified() );
|
|
if ( hasUnsavedEdits )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hasUnsavedEdits )
|
|
{
|
|
markDirty();
|
|
whyDirty = QStringLiteral( "<p style='color:darkred;'>" );
|
|
whyDirty += tr( "Project has layer(s) in edit mode with unsaved edits, which will NOT be saved!" );
|
|
whyDirty += QLatin1String( "</p>" );
|
|
}
|
|
}
|
|
|
|
QMessageBox::StandardButton answer( QMessageBox::Discard );
|
|
freezeCanvases();
|
|
|
|
//QgsDebugMsg(QString("Layer count is %1").arg(mMapCanvas->layerCount()));
|
|
//QgsDebugMsg(QString("Project is %1dirty").arg( QgsProject::instance()->isDirty() ? "" : "not "));
|
|
//QgsDebugMsg(QString("Map canvas is %1dirty").arg(mMapCanvas->isDirty() ? "" : "not "));
|
|
|
|
QgsSettings settings;
|
|
bool askThem = settings.value( QStringLiteral( "qgis/askToSaveProjectChanges" ), true ).toBool();
|
|
|
|
if ( askThem && QgsProject::instance()->isDirty() && QgsProject::instance()->count() > 0 )
|
|
{
|
|
// flag project as dirty since dirty state of canvas is reset if "dirty"
|
|
// is based on a zoom or pan
|
|
markDirty();
|
|
|
|
// old code: mProjectIsDirtyFlag = true;
|
|
|
|
// prompt user to save
|
|
answer = QMessageBox::information( this, tr( "Save?" ),
|
|
tr( "Do you want to save the current project? %1" )
|
|
.arg( whyDirty ),
|
|
QMessageBox::Save | QMessageBox::Cancel | QMessageBox::Discard,
|
|
hasUnsavedEdits ? QMessageBox::Cancel : QMessageBox::Save );
|
|
if ( QMessageBox::Save == answer )
|
|
{
|
|
if ( !fileSave() )
|
|
answer = QMessageBox::Cancel;
|
|
}
|
|
}
|
|
|
|
freezeCanvases( false );
|
|
|
|
return answer != QMessageBox::Cancel;
|
|
}
|
|
|
|
bool QgisApp::checkTasksDependOnProject()
|
|
{
|
|
QSet< QString > activeTaskDescriptions;
|
|
QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
|
|
QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
|
|
|
|
for ( ; layerIt != layers.constEnd(); ++layerIt )
|
|
{
|
|
QList< QgsTask * > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layerIt.value() );
|
|
if ( !tasks.isEmpty() )
|
|
{
|
|
Q_FOREACH ( QgsTask *task, tasks )
|
|
{
|
|
activeTaskDescriptions.insert( trUtf8( " • %1" ).arg( task->description() ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !activeTaskDescriptions.isEmpty() )
|
|
{
|
|
QMessageBox::warning( this, tr( "Active tasks" ),
|
|
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( activeTaskDescriptions.toList().join( QStringLiteral( "\n" ) ) ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QgisApp::closeProject()
|
|
{
|
|
// unload the project macros before changing anything
|
|
if ( mTrustedMacros )
|
|
{
|
|
QgsPythonRunner::run( QStringLiteral( "qgis.utils.unloadProjectMacros();" ) );
|
|
}
|
|
|
|
mTrustedMacros = false;
|
|
|
|
mLegendExpressionFilterButton->setExpressionText( QLatin1String( "" ) );
|
|
mLegendExpressionFilterButton->setChecked( false );
|
|
mActionFilterLegend->setChecked( false );
|
|
|
|
closeAdditionalMapCanvases();
|
|
|
|
deletePrintComposers();
|
|
removeAnnotationItems();
|
|
// clear out any stuff from project
|
|
mMapCanvas->freeze( true );
|
|
mMapCanvas->setLayers( QList<QgsMapLayer *>() );
|
|
mMapCanvas->clearCache();
|
|
mOverviewCanvas->setLayers( QList<QgsMapLayer *>() );
|
|
mMapCanvas->freeze( false );
|
|
QgsProject::instance()->clear();
|
|
}
|
|
|
|
|
|
void QgisApp::changeEvent( QEvent *event )
|
|
{
|
|
QMainWindow::changeEvent( event );
|
|
#ifdef Q_OS_MAC
|
|
switch ( event->type() )
|
|
{
|
|
case QEvent::ActivationChange:
|
|
if ( QApplication::activeWindow() == this )
|
|
{
|
|
mWindowAction->setChecked( true );
|
|
}
|
|
// this should not be necessary since the action is part of an action group
|
|
// however this check is not cleared if PrintComposer is closed and reopened
|
|
else
|
|
{
|
|
mWindowAction->setChecked( false );
|
|
}
|
|
break;
|
|
|
|
case QEvent::WindowTitleChange:
|
|
mWindowAction->setText( windowTitle() );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::closeEvent( QCloseEvent *event )
|
|
{
|
|
// We'll close in our own good time, thank you very much
|
|
event->ignore();
|
|
// Do the usual checks and ask if they want to save, etc
|
|
fileExit();
|
|
}
|
|
|
|
|
|
void QgisApp::whatsThis()
|
|
{
|
|
QWhatsThis::enterWhatsThisMode();
|
|
} // QgisApp::whatsThis()
|
|
|
|
QMenu *QgisApp::getPluginMenu( const QString &menuName )
|
|
{
|
|
/* Plugin menu items are below the plugin separator (which may not exist yet
|
|
* if no plugins are loaded) and above the python separator. If python is not
|
|
* present, there is no python separator and the plugin list is at the bottom
|
|
* of the menu.
|
|
*/
|
|
|
|
QString cleanedMenuName = menuName;
|
|
#ifdef Q_OS_MAC
|
|
// Mac doesn't have '&' keyboard shortcuts.
|
|
cleanedMenuName.remove( QChar( '&' ) );
|
|
#endif
|
|
QAction *before = mActionPluginSeparator2; // python separator or end of list
|
|
if ( !mActionPluginSeparator1 )
|
|
{
|
|
// First plugin - create plugin list separator
|
|
mActionPluginSeparator1 = mPluginMenu->insertSeparator( before );
|
|
}
|
|
else
|
|
{
|
|
QString dst = cleanedMenuName;
|
|
dst.remove( QChar( '&' ) );
|
|
|
|
// Plugins exist - search between plugin separator and python separator or end of list
|
|
QList<QAction *> actions = mPluginMenu->actions();
|
|
int end = mActionPluginSeparator2 ? actions.indexOf( mActionPluginSeparator2 ) : actions.count();
|
|
for ( int i = actions.indexOf( mActionPluginSeparator1 ) + 1; i < end; i++ )
|
|
{
|
|
QString src = actions.at( i )->text();
|
|
src.remove( QChar( '&' ) );
|
|
|
|
int comp = dst.localeAwareCompare( src );
|
|
if ( comp < 0 )
|
|
{
|
|
// Add item before this one
|
|
before = actions.at( i );
|
|
break;
|
|
}
|
|
else if ( comp == 0 )
|
|
{
|
|
// Plugin menu item already exists
|
|
return actions.at( i )->menu();
|
|
}
|
|
}
|
|
}
|
|
// It doesn't exist, so create
|
|
QMenu *menu = new QMenu( cleanedMenuName, this );
|
|
menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
|
|
// Where to put it? - we worked that out above...
|
|
mPluginMenu->insertMenu( before, menu );
|
|
|
|
return menu;
|
|
}
|
|
|
|
void QgisApp::addPluginToMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getPluginMenu( name );
|
|
menu->addAction( action );
|
|
}
|
|
|
|
void QgisApp::removePluginMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getPluginMenu( name );
|
|
menu->removeAction( action );
|
|
if ( menu->actions().isEmpty() )
|
|
{
|
|
mPluginMenu->removeAction( menu->menuAction() );
|
|
}
|
|
// Remove separator above plugins in Plugin menu if no plugins remain
|
|
QList<QAction *> actions = mPluginMenu->actions();
|
|
int end = mActionPluginSeparator2 ? actions.indexOf( mActionPluginSeparator2 ) : actions.count();
|
|
if ( actions.indexOf( mActionPluginSeparator1 ) + 1 == end )
|
|
{
|
|
mPluginMenu->removeAction( mActionPluginSeparator1 );
|
|
mActionPluginSeparator1 = nullptr;
|
|
}
|
|
}
|
|
|
|
QMenu *QgisApp::getDatabaseMenu( const QString &menuName )
|
|
{
|
|
QString cleanedMenuName = menuName;
|
|
#ifdef Q_OS_MAC
|
|
// Mac doesn't have '&' keyboard shortcuts.
|
|
cleanedMenuName.remove( QChar( '&' ) );
|
|
#endif
|
|
QString dst = cleanedMenuName;
|
|
dst.remove( QChar( '&' ) );
|
|
|
|
QAction *before = nullptr;
|
|
QList<QAction *> actions = mDatabaseMenu->actions();
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
QString src = actions.at( i )->text();
|
|
src.remove( QChar( '&' ) );
|
|
|
|
int comp = dst.localeAwareCompare( src );
|
|
if ( comp < 0 )
|
|
{
|
|
// Add item before this one
|
|
before = actions.at( i );
|
|
break;
|
|
}
|
|
else if ( comp == 0 )
|
|
{
|
|
// Plugin menu item already exists
|
|
return actions.at( i )->menu();
|
|
}
|
|
}
|
|
// It doesn't exist, so create
|
|
QMenu *menu = new QMenu( cleanedMenuName, this );
|
|
menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
|
|
if ( before )
|
|
mDatabaseMenu->insertMenu( before, menu );
|
|
else
|
|
mDatabaseMenu->addMenu( menu );
|
|
|
|
return menu;
|
|
}
|
|
|
|
QMenu *QgisApp::getRasterMenu( const QString &menuName )
|
|
{
|
|
QString cleanedMenuName = menuName;
|
|
#ifdef Q_OS_MAC
|
|
// Mac doesn't have '&' keyboard shortcuts.
|
|
cleanedMenuName.remove( QChar( '&' ) );
|
|
#endif
|
|
|
|
QAction *before = nullptr;
|
|
if ( !mActionRasterSeparator )
|
|
{
|
|
// First plugin - create plugin list separator
|
|
mActionRasterSeparator = mRasterMenu->insertSeparator( before );
|
|
}
|
|
else
|
|
{
|
|
QString dst = cleanedMenuName;
|
|
dst.remove( QChar( '&' ) );
|
|
// Plugins exist - search between plugin separator and python separator or end of list
|
|
QList<QAction *> actions = mRasterMenu->actions();
|
|
for ( int i = actions.indexOf( mActionRasterSeparator ) + 1; i < actions.count(); i++ )
|
|
{
|
|
QString src = actions.at( i )->text();
|
|
src.remove( QChar( '&' ) );
|
|
|
|
int comp = dst.localeAwareCompare( src );
|
|
if ( comp < 0 )
|
|
{
|
|
// Add item before this one
|
|
before = actions.at( i );
|
|
break;
|
|
}
|
|
else if ( comp == 0 )
|
|
{
|
|
// Plugin menu item already exists
|
|
return actions.at( i )->menu();
|
|
}
|
|
}
|
|
}
|
|
|
|
// It doesn't exist, so create
|
|
QMenu *menu = new QMenu( cleanedMenuName, this );
|
|
menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
|
|
if ( before )
|
|
mRasterMenu->insertMenu( before, menu );
|
|
else
|
|
mRasterMenu->addMenu( menu );
|
|
|
|
return menu;
|
|
}
|
|
|
|
QMenu *QgisApp::getVectorMenu( const QString &menuName )
|
|
{
|
|
QString cleanedMenuName = menuName;
|
|
#ifdef Q_OS_MAC
|
|
// Mac doesn't have '&' keyboard shortcuts.
|
|
cleanedMenuName.remove( QChar( '&' ) );
|
|
#endif
|
|
QString dst = cleanedMenuName;
|
|
dst.remove( QChar( '&' ) );
|
|
|
|
QAction *before = nullptr;
|
|
QList<QAction *> actions = mVectorMenu->actions();
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
QString src = actions.at( i )->text();
|
|
src.remove( QChar( '&' ) );
|
|
|
|
int comp = dst.localeAwareCompare( src );
|
|
if ( comp < 0 )
|
|
{
|
|
// Add item before this one
|
|
before = actions.at( i );
|
|
break;
|
|
}
|
|
else if ( comp == 0 )
|
|
{
|
|
// Plugin menu item already exists
|
|
return actions.at( i )->menu();
|
|
}
|
|
}
|
|
// It doesn't exist, so create
|
|
QMenu *menu = new QMenu( cleanedMenuName, this );
|
|
menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
|
|
if ( before )
|
|
mVectorMenu->insertMenu( before, menu );
|
|
else
|
|
mVectorMenu->addMenu( menu );
|
|
|
|
return menu;
|
|
}
|
|
|
|
QMenu *QgisApp::getWebMenu( const QString &menuName )
|
|
{
|
|
QString cleanedMenuName = menuName;
|
|
#ifdef Q_OS_MAC
|
|
// Mac doesn't have '&' keyboard shortcuts.
|
|
cleanedMenuName.remove( QChar( '&' ) );
|
|
#endif
|
|
QString dst = cleanedMenuName;
|
|
dst.remove( QChar( '&' ) );
|
|
|
|
QAction *before = nullptr;
|
|
QList<QAction *> actions = mWebMenu->actions();
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
QString src = actions.at( i )->text();
|
|
src.remove( QChar( '&' ) );
|
|
|
|
int comp = dst.localeAwareCompare( src );
|
|
if ( comp < 0 )
|
|
{
|
|
// Add item before this one
|
|
before = actions.at( i );
|
|
break;
|
|
}
|
|
else if ( comp == 0 )
|
|
{
|
|
// Plugin menu item already exists
|
|
return actions.at( i )->menu();
|
|
}
|
|
}
|
|
// It doesn't exist, so create
|
|
QMenu *menu = new QMenu( cleanedMenuName, this );
|
|
menu->setObjectName( normalizedMenuName( cleanedMenuName ) );
|
|
if ( before )
|
|
mWebMenu->insertMenu( before, menu );
|
|
else
|
|
mWebMenu->addMenu( menu );
|
|
|
|
return menu;
|
|
}
|
|
|
|
void QgisApp::insertAddLayerAction( QAction *action )
|
|
{
|
|
mAddLayerMenu->insertAction( mActionAddLayerSeparator, action );
|
|
}
|
|
|
|
void QgisApp::removeAddLayerAction( QAction *action )
|
|
{
|
|
mAddLayerMenu->removeAction( action );
|
|
}
|
|
|
|
void QgisApp::addPluginToDatabaseMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getDatabaseMenu( name );
|
|
menu->addAction( action );
|
|
|
|
// add the Database menu to the menuBar if not added yet
|
|
if ( mDatabaseMenu->actions().count() != 1 )
|
|
return;
|
|
|
|
QAction *before = nullptr;
|
|
QList<QAction *> actions = menuBar()->actions();
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
if ( actions.at( i )->menu() == mDatabaseMenu )
|
|
return;
|
|
|
|
// goes before Web menu, if present
|
|
if ( actions.at( i )->menu() == mWebMenu )
|
|
{
|
|
before = actions.at( i );
|
|
break;
|
|
}
|
|
}
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
// defaults to after Raster menu, which is already in qgisapp.ui
|
|
if ( actions.at( i )->menu() == mRasterMenu )
|
|
{
|
|
if ( !before )
|
|
{
|
|
before = actions.at( i += 1 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( before )
|
|
menuBar()->insertMenu( before, mDatabaseMenu );
|
|
else
|
|
// fallback insert
|
|
menuBar()->insertMenu( firstRightStandardMenu()->menuAction(), mDatabaseMenu );
|
|
}
|
|
|
|
void QgisApp::addPluginToRasterMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getRasterMenu( name );
|
|
menu->addAction( action );
|
|
}
|
|
|
|
void QgisApp::addPluginToVectorMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getVectorMenu( name );
|
|
menu->addAction( action );
|
|
}
|
|
|
|
void QgisApp::addPluginToWebMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getWebMenu( name );
|
|
menu->addAction( action );
|
|
|
|
// add the Vector menu to the menuBar if not added yet
|
|
if ( mWebMenu->actions().count() != 1 )
|
|
return;
|
|
|
|
QAction *before = nullptr;
|
|
QList<QAction *> actions = menuBar()->actions();
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
// goes after Database menu, if present
|
|
if ( actions.at( i )->menu() == mDatabaseMenu )
|
|
{
|
|
before = actions.at( i += 1 );
|
|
// don't break here
|
|
}
|
|
|
|
if ( actions.at( i )->menu() == mWebMenu )
|
|
return;
|
|
}
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
// defaults to after Raster menu, which is already in qgisapp.ui
|
|
if ( actions.at( i )->menu() == mRasterMenu )
|
|
{
|
|
if ( !before )
|
|
{
|
|
before = actions.at( i += 1 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( before )
|
|
menuBar()->insertMenu( before, mWebMenu );
|
|
else
|
|
// fallback insert
|
|
menuBar()->insertMenu( firstRightStandardMenu()->menuAction(), mWebMenu );
|
|
}
|
|
|
|
void QgisApp::removePluginDatabaseMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getDatabaseMenu( name );
|
|
menu->removeAction( action );
|
|
if ( menu->actions().isEmpty() )
|
|
{
|
|
mDatabaseMenu->removeAction( menu->menuAction() );
|
|
}
|
|
|
|
// remove the Database menu from the menuBar if there are no more actions
|
|
if ( !mDatabaseMenu->actions().isEmpty() )
|
|
return;
|
|
|
|
QList<QAction *> actions = menuBar()->actions();
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
if ( actions.at( i )->menu() == mDatabaseMenu )
|
|
{
|
|
menuBar()->removeAction( actions.at( i ) );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::removePluginRasterMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getRasterMenu( name );
|
|
menu->removeAction( action );
|
|
if ( menu->actions().isEmpty() )
|
|
{
|
|
mRasterMenu->removeAction( menu->menuAction() );
|
|
}
|
|
|
|
// Remove separator above plugins in Raster menu if no plugins remain
|
|
QList<QAction *> actions = mRasterMenu->actions();
|
|
if ( actions.indexOf( mActionRasterSeparator ) + 1 == actions.count() )
|
|
{
|
|
mRasterMenu->removeAction( mActionRasterSeparator );
|
|
mActionRasterSeparator = nullptr;
|
|
}
|
|
}
|
|
|
|
void QgisApp::removePluginVectorMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getVectorMenu( name );
|
|
menu->removeAction( action );
|
|
if ( menu->actions().isEmpty() )
|
|
{
|
|
mVectorMenu->removeAction( menu->menuAction() );
|
|
}
|
|
|
|
// remove the Vector menu from the menuBar if there are no more actions
|
|
if ( !mVectorMenu->actions().isEmpty() )
|
|
return;
|
|
|
|
QList<QAction *> actions = menuBar()->actions();
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
if ( actions.at( i )->menu() == mVectorMenu )
|
|
{
|
|
menuBar()->removeAction( actions.at( i ) );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::removePluginWebMenu( const QString &name, QAction *action )
|
|
{
|
|
QMenu *menu = getWebMenu( name );
|
|
menu->removeAction( action );
|
|
if ( menu->actions().isEmpty() )
|
|
{
|
|
mWebMenu->removeAction( menu->menuAction() );
|
|
}
|
|
|
|
// remove the Web menu from the menuBar if there are no more actions
|
|
if ( !mWebMenu->actions().isEmpty() )
|
|
return;
|
|
|
|
QList<QAction *> actions = menuBar()->actions();
|
|
for ( int i = 0; i < actions.count(); i++ )
|
|
{
|
|
if ( actions.at( i )->menu() == mWebMenu )
|
|
{
|
|
menuBar()->removeAction( actions.at( i ) );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
int QgisApp::addPluginToolBarIcon( QAction *qAction )
|
|
{
|
|
mPluginToolBar->addAction( qAction );
|
|
return 0;
|
|
}
|
|
|
|
QAction *QgisApp::addPluginToolBarWidget( QWidget *widget )
|
|
{
|
|
return mPluginToolBar->addWidget( widget );
|
|
}
|
|
|
|
void QgisApp::removePluginToolBarIcon( QAction *qAction )
|
|
{
|
|
mPluginToolBar->removeAction( qAction );
|
|
}
|
|
|
|
int QgisApp::addRasterToolBarIcon( QAction *qAction )
|
|
{
|
|
mRasterToolBar->addAction( qAction );
|
|
return 0;
|
|
}
|
|
|
|
QAction *QgisApp::addRasterToolBarWidget( QWidget *widget )
|
|
{
|
|
return mRasterToolBar->addWidget( widget );
|
|
}
|
|
|
|
void QgisApp::removeRasterToolBarIcon( QAction *qAction )
|
|
{
|
|
mRasterToolBar->removeAction( qAction );
|
|
}
|
|
|
|
int QgisApp::addVectorToolBarIcon( QAction *qAction )
|
|
{
|
|
mVectorToolBar->addAction( qAction );
|
|
return 0;
|
|
}
|
|
|
|
QAction *QgisApp::addVectorToolBarWidget( QWidget *widget )
|
|
{
|
|
return mVectorToolBar->addWidget( widget );
|
|
}
|
|
|
|
void QgisApp::removeVectorToolBarIcon( QAction *qAction )
|
|
{
|
|
mVectorToolBar->removeAction( qAction );
|
|
}
|
|
|
|
int QgisApp::addDatabaseToolBarIcon( QAction *qAction )
|
|
{
|
|
mDatabaseToolBar->addAction( qAction );
|
|
return 0;
|
|
}
|
|
|
|
QAction *QgisApp::addDatabaseToolBarWidget( QWidget *widget )
|
|
{
|
|
return mDatabaseToolBar->addWidget( widget );
|
|
}
|
|
|
|
void QgisApp::removeDatabaseToolBarIcon( QAction *qAction )
|
|
{
|
|
mDatabaseToolBar->removeAction( qAction );
|
|
}
|
|
|
|
int QgisApp::addWebToolBarIcon( QAction *qAction )
|
|
{
|
|
mWebToolBar->addAction( qAction );
|
|
return 0;
|
|
}
|
|
|
|
QAction *QgisApp::addWebToolBarWidget( QWidget *widget )
|
|
{
|
|
return mWebToolBar->addWidget( widget );
|
|
}
|
|
|
|
void QgisApp::removeWebToolBarIcon( QAction *qAction )
|
|
{
|
|
mWebToolBar->removeAction( qAction );
|
|
}
|
|
|
|
void QgisApp::updateCrsStatusBar()
|
|
{
|
|
if ( QgsProject::instance()->crs().isValid() )
|
|
{
|
|
mOnTheFlyProjectionStatusButton->setText( QgsProject::instance()->crs().authid() );
|
|
|
|
mOnTheFlyProjectionStatusButton->setToolTip(
|
|
tr( "Current CRS: %1" ).arg( QgsProject::instance()->crs().description() ) );
|
|
mOnTheFlyProjectionStatusButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconProjectionEnabled.svg" ) ) );
|
|
}
|
|
else
|
|
{
|
|
mOnTheFlyProjectionStatusButton->setText( QString() );
|
|
mOnTheFlyProjectionStatusButton->setToolTip( tr( "No projection" ) );
|
|
mOnTheFlyProjectionStatusButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconProjectionDisabled.svg" ) ) );
|
|
}
|
|
}
|
|
|
|
// slot to update the progress bar in the status bar
|
|
void QgisApp::showProgress( int progress, int totalSteps )
|
|
{
|
|
if ( progress == totalSteps )
|
|
{
|
|
mProgressBar->reset();
|
|
mProgressBar->hide();
|
|
}
|
|
else
|
|
{
|
|
//only call show if not already hidden to reduce flicker
|
|
if ( !mProgressBar->isVisible() )
|
|
{
|
|
mProgressBar->show();
|
|
}
|
|
mProgressBar->setMaximum( totalSteps );
|
|
mProgressBar->setValue( progress );
|
|
|
|
if ( mProgressBar->maximum() == 0 )
|
|
{
|
|
// for busy indicator (when minimum equals to maximum) the oxygen Qt style (used in KDE)
|
|
// has some issues and does not start busy indicator animation. This is an ugly fix
|
|
// that forces creation of a temporary progress bar that somehow resumes the animations.
|
|
// Caution: looking at the code below may introduce mild pain in stomach.
|
|
if ( strcmp( QApplication::style()->metaObject()->className(), "Oxygen::Style" ) == 0 )
|
|
{
|
|
QProgressBar pb;
|
|
pb.setAttribute( Qt::WA_DontShowOnScreen ); // no visual annoyance
|
|
pb.setMaximum( 0 );
|
|
pb.show();
|
|
qApp->processEvents();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void QgisApp::mapToolChanged( QgsMapTool *newTool, QgsMapTool *oldTool )
|
|
{
|
|
if ( oldTool )
|
|
{
|
|
disconnect( oldTool, &QgsMapTool::messageEmitted, this, &QgisApp::displayMapToolMessage );
|
|
disconnect( oldTool, &QgsMapTool::messageEmitted, this, &QgisApp::displayMapToolMessage );
|
|
disconnect( oldTool, &QgsMapTool::messageDiscarded, this, &QgisApp::removeMapToolMessage );
|
|
}
|
|
|
|
if ( newTool )
|
|
{
|
|
if ( !( newTool->flags() & QgsMapTool::EditTool ) )
|
|
{
|
|
mNonEditMapTool = newTool;
|
|
}
|
|
|
|
connect( newTool, &QgsMapTool::messageEmitted, this, &QgisApp::displayMapToolMessage );
|
|
connect( newTool, &QgsMapTool::messageEmitted, this, &QgisApp::displayMapToolMessage );
|
|
connect( newTool, &QgsMapTool::messageDiscarded, this, &QgisApp::removeMapToolMessage );
|
|
}
|
|
}
|
|
|
|
void QgisApp::showMapCanvas()
|
|
{
|
|
// Map layers changed -> switch to map canvas
|
|
if ( mCentralContainer )
|
|
mCentralContainer->setCurrentIndex( 0 );
|
|
}
|
|
|
|
void QgisApp::markDirty()
|
|
{
|
|
// notify the project that there was a change
|
|
QgsProject::instance()->setDirty( true );
|
|
}
|
|
|
|
void QgisApp::extentChanged()
|
|
{
|
|
// allow symbols in the legend update their preview if they use map units
|
|
mLayerTreeView->layerTreeModel()->setLegendMapViewData( mMapCanvas->mapUnitsPerPixel(), mMapCanvas->mapSettings().outputDpi(), mMapCanvas->scale() );
|
|
}
|
|
|
|
void QgisApp::layersWereAdded( const QList<QgsMapLayer *> &layers )
|
|
{
|
|
Q_FOREACH ( QgsMapLayer *layer, layers )
|
|
{
|
|
QgsDataProvider *provider = nullptr;
|
|
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
|
|
if ( vlayer )
|
|
{
|
|
// notify user about any font family substitution, but only when rendering labels (i.e. not when opening settings dialog)
|
|
connect( vlayer, &QgsVectorLayer::labelingFontNotFound, this, &QgisApp::labelingFontNotFound );
|
|
|
|
QgsVectorDataProvider *vProvider = vlayer->dataProvider();
|
|
if ( vProvider && vProvider->capabilities() & QgsVectorDataProvider::EditingCapabilities )
|
|
{
|
|
connect( vlayer, &QgsVectorLayer::layerModified, this, &QgisApp::updateLayerModifiedActions );
|
|
connect( vlayer, &QgsVectorLayer::editingStarted, this, &QgisApp::layerEditStateChanged );
|
|
connect( vlayer, &QgsVectorLayer::editingStopped, this, &QgisApp::layerEditStateChanged );
|
|
connect( vlayer, &QgsVectorLayer::readOnlyChanged, this, &QgisApp::layerEditStateChanged );
|
|
}
|
|
|
|
connect( vlayer, &QgsVectorLayer::raiseError, this, &QgisApp::onLayerError );
|
|
|
|
provider = vProvider;
|
|
}
|
|
|
|
QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layer );
|
|
if ( rlayer )
|
|
{
|
|
// connect up any request the raster may make to update the statusbar message
|
|
connect( rlayer, &QgsRasterLayer::statusChanged, this, &QgisApp::showStatusMessage );
|
|
|
|
provider = rlayer->dataProvider();
|
|
}
|
|
|
|
if ( provider )
|
|
{
|
|
connect( provider, &QgsDataProvider::dataChanged, layer, [layer] { layer->triggerRepaint(); } );
|
|
connect( provider, &QgsDataProvider::dataChanged, this, &QgisApp::refreshMapCanvas );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::showRotation()
|
|
{
|
|
// update the statusbar with the current rotation.
|
|
double myrotation = mMapCanvas->rotation();
|
|
mRotationEdit->setValue( myrotation );
|
|
} // QgisApp::showRotation
|
|
|
|
|
|
void QgisApp::updateMouseCoordinatePrecision()
|
|
{
|
|
mCoordsEdit->setMouseCoordinatesPrecision( QgsCoordinateUtils::calculateCoordinatePrecision( mapCanvas()->mapUnitsPerPixel(), mapCanvas()->mapSettings().destinationCrs() ) );
|
|
}
|
|
|
|
void QgisApp::showStatusMessage( const QString &message )
|
|
{
|
|
mStatusBar->showMessage( message );
|
|
}
|
|
|
|
void QgisApp::displayMapToolMessage( const QString &message, QgsMessageBar::MessageLevel level )
|
|
{
|
|
// remove previous message
|
|
messageBar()->popWidget( mLastMapToolMessage );
|
|
|
|
QgsMapTool *tool = mapCanvas()->mapTool();
|
|
|
|
if ( tool )
|
|
{
|
|
mLastMapToolMessage = new QgsMessageBarItem( tool->toolName(), message, level, messageTimeout() );
|
|
messageBar()->pushItem( mLastMapToolMessage );
|
|
}
|
|
}
|
|
|
|
void QgisApp::displayMessage( const QString &title, const QString &message, QgsMessageBar::MessageLevel level )
|
|
{
|
|
messageBar()->pushMessage( title, message, level, messageTimeout() );
|
|
}
|
|
|
|
void QgisApp::removeMapToolMessage()
|
|
{
|
|
// remove previous message
|
|
messageBar()->popWidget( mLastMapToolMessage );
|
|
}
|
|
|
|
|
|
// Show the maptip using tooltip
|
|
void QgisApp::showMapTip()
|
|
{
|
|
QPoint myPointerPos = mMapCanvas->mouseLastXY();
|
|
|
|
// Make sure there is an active layer before proceeding
|
|
QgsMapLayer *mypLayer = mMapCanvas->currentLayer();
|
|
if ( mypLayer )
|
|
{
|
|
//QgsDebugMsg("Current layer for maptip display is: " + mypLayer->source());
|
|
// only process vector layers
|
|
if ( mypLayer->type() == QgsMapLayer::VectorLayer )
|
|
{
|
|
// Show the maptip if the maptips button is depressed
|
|
if ( mMapTipsVisible )
|
|
{
|
|
mpMaptip->showMapTip( mypLayer, mLastMapPosition, myPointerPos, mMapCanvas );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::projectPropertiesProjections()
|
|
{
|
|
// Driver to display the project props dialog and switch to the
|
|
// projections tab
|
|
mShowProjectionTab = true;
|
|
projectProperties();
|
|
}
|
|
|
|
void QgisApp::projectProperties()
|
|
{
|
|
/* Display the property sheet for the Project */
|
|
// set wait cursor since construction of the project properties
|
|
// dialog results in the construction of the spatial reference
|
|
// system QMap
|
|
QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
QgsProjectProperties *pp = new QgsProjectProperties( mMapCanvas, this );
|
|
// if called from the status bar, show the projection tab
|
|
if ( mShowProjectionTab )
|
|
{
|
|
pp->showProjectionsTab();
|
|
mShowProjectionTab = false;
|
|
}
|
|
qApp->processEvents();
|
|
// Be told if the mouse display precision may have changed by the user
|
|
// changing things in the project properties dialog box
|
|
connect( pp, &QgsProjectProperties::displayPrecisionChanged, this,
|
|
&QgisApp::updateMouseCoordinatePrecision );
|
|
|
|
connect( pp, &QgsProjectProperties::scalesChanged, mScaleWidget,
|
|
&QgsStatusBarScaleWidget::updateScales );
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
// Display the modal dialog box.
|
|
pp->exec();
|
|
|
|
qobject_cast<QgsMeasureTool *>( mMapTools.mMeasureDist )->updateSettings();
|
|
qobject_cast<QgsMeasureTool *>( mMapTools.mMeasureArea )->updateSettings();
|
|
qobject_cast<QgsMapToolMeasureAngle *>( mMapTools.mMeasureAngle )->updateSettings();
|
|
|
|
// Set the window title.
|
|
setTitleBarText_( *this );
|
|
|
|
// delete the property sheet object
|
|
delete pp;
|
|
}
|
|
|
|
|
|
QgsClipboard *QgisApp::clipboard()
|
|
{
|
|
return mInternalClipboard;
|
|
}
|
|
|
|
void QgisApp::selectionChanged( QgsMapLayer *layer )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
|
|
if ( vlayer )
|
|
{
|
|
showStatusMessage( tr( "%n feature(s) selected on layer %1.", "number of selected features", vlayer->selectedFeatureCount() ).arg( vlayer->name() ) );
|
|
}
|
|
if ( layer == activeLayer() )
|
|
{
|
|
activateDeactivateLayerRelatedActions( layer );
|
|
}
|
|
}
|
|
|
|
void QgisApp::legendLayerSelectionChanged()
|
|
{
|
|
QList<QgsLayerTreeLayer *> selectedLayers = mLayerTreeView ? mLayerTreeView->selectedLayerNodes() : QList<QgsLayerTreeLayer *>();
|
|
|
|
mActionDuplicateLayer->setEnabled( !selectedLayers.isEmpty() );
|
|
mActionSetLayerScaleVisibility->setEnabled( !selectedLayers.isEmpty() );
|
|
mActionSetLayerCRS->setEnabled( !selectedLayers.isEmpty() );
|
|
mActionSetProjectCRSFromLayer->setEnabled( selectedLayers.count() == 1 );
|
|
|
|
mActionSaveEdits->setEnabled( QgsLayerTreeUtils::layersModified( selectedLayers ) );
|
|
mActionRollbackEdits->setEnabled( QgsLayerTreeUtils::layersModified( selectedLayers ) );
|
|
mActionCancelEdits->setEnabled( QgsLayerTreeUtils::layersEditable( selectedLayers ) );
|
|
|
|
mLegendExpressionFilterButton->setEnabled( false );
|
|
mLegendExpressionFilterButton->setVectorLayer( nullptr );
|
|
if ( selectedLayers.size() == 1 )
|
|
{
|
|
QgsLayerTreeLayer *l = selectedLayers.front();
|
|
if ( l->layer() && l->layer()->type() == QgsMapLayer::VectorLayer )
|
|
{
|
|
mLegendExpressionFilterButton->setEnabled( true );
|
|
bool exprEnabled;
|
|
QString expr = QgsLayerTreeUtils::legendFilterByExpression( *l, &exprEnabled );
|
|
mLegendExpressionFilterButton->setExpressionText( expr );
|
|
mLegendExpressionFilterButton->setVectorLayer( qobject_cast<QgsVectorLayer *>( l->layer() ) );
|
|
mLegendExpressionFilterButton->setChecked( exprEnabled );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::layerEditStateChanged()
|
|
{
|
|
QgsMapLayer *layer = qobject_cast<QgsMapLayer *>( sender() );
|
|
if ( layer && layer == activeLayer() )
|
|
{
|
|
activateDeactivateLayerRelatedActions( layer );
|
|
mSaveRollbackInProgress = false;
|
|
}
|
|
}
|
|
|
|
void QgisApp::updateLabelToolButtons()
|
|
{
|
|
bool enableMove = false, enableRotate = false, enablePin = false, enableShowHide = false, enableChange = false;
|
|
|
|
QMap<QString, QgsMapLayer *> layers = QgsProject::instance()->mapLayers();
|
|
for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( it.value() );
|
|
if ( !vlayer || !vlayer->isEditable() ||
|
|
( !vlayer->diagramsEnabled() && !vlayer->labelsEnabled() ) )
|
|
continue;
|
|
|
|
int colX, colY, colShow, colAng;
|
|
enablePin =
|
|
enablePin ||
|
|
( qobject_cast<QgsMapToolPinLabels *>( mMapTools.mPinLabels ) &&
|
|
( qobject_cast<QgsMapToolPinLabels *>( mMapTools.mPinLabels )->labelMoveable( vlayer, colX, colY )
|
|
|| qobject_cast<QgsMapToolPinLabels *>( mMapTools.mPinLabels )->diagramMoveable( vlayer, colX, colY ) ) );
|
|
|
|
enableShowHide =
|
|
enableShowHide ||
|
|
( qobject_cast<QgsMapToolShowHideLabels *>( mMapTools.mShowHideLabels ) &&
|
|
( qobject_cast<QgsMapToolShowHideLabels *>( mMapTools.mShowHideLabels )->labelCanShowHide( vlayer, colShow )
|
|
|| qobject_cast<QgsMapToolShowHideLabels *>( mMapTools.mShowHideLabels )->diagramCanShowHide( vlayer, colShow ) ) );
|
|
|
|
enableMove =
|
|
enableMove ||
|
|
( qobject_cast<QgsMapToolMoveLabel *>( mMapTools.mMoveLabel ) &&
|
|
( qobject_cast<QgsMapToolMoveLabel *>( mMapTools.mMoveLabel )->labelMoveable( vlayer, colX, colY )
|
|
|| qobject_cast<QgsMapToolMoveLabel *>( mMapTools.mMoveLabel )->diagramMoveable( vlayer, colX, colY ) ) );
|
|
|
|
enableRotate =
|
|
enableRotate ||
|
|
( qobject_cast<QgsMapToolRotateLabel *>( mMapTools.mRotateLabel ) &&
|
|
qobject_cast<QgsMapToolRotateLabel *>( mMapTools.mRotateLabel )->layerIsRotatable( vlayer, colAng ) );
|
|
|
|
enableChange = true;
|
|
|
|
if ( enablePin && enableShowHide && enableMove && enableRotate && enableChange )
|
|
break;
|
|
}
|
|
|
|
mActionPinLabels->setEnabled( enablePin );
|
|
mActionShowHideLabels->setEnabled( enableShowHide );
|
|
mActionMoveLabel->setEnabled( enableMove );
|
|
mActionRotateLabel->setEnabled( enableRotate );
|
|
mActionChangeLabelProperties->setEnabled( enableChange );
|
|
}
|
|
|
|
void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer *layer )
|
|
{
|
|
updateLabelToolButtons();
|
|
|
|
mMenuPasteAs->setEnabled( clipboard() && !clipboard()->isEmpty() );
|
|
mActionPasteAsNewVector->setEnabled( clipboard() && !clipboard()->isEmpty() );
|
|
mActionPasteAsNewMemoryVector->setEnabled( clipboard() && !clipboard()->isEmpty() );
|
|
|
|
updateLayerModifiedActions();
|
|
|
|
if ( !layer )
|
|
{
|
|
mActionSelectFeatures->setEnabled( false );
|
|
mActionSelectPolygon->setEnabled( false );
|
|
mActionSelectFreehand->setEnabled( false );
|
|
mActionSelectRadius->setEnabled( false );
|
|
mActionIdentify->setEnabled( QgsSettings().value( QStringLiteral( "/Map/identifyMode" ), 0 ).toInt() != 0 );
|
|
mActionSelectByExpression->setEnabled( false );
|
|
mActionSelectByForm->setEnabled( false );
|
|
mActionLabeling->setEnabled( false );
|
|
mActionOpenTable->setEnabled( false );
|
|
mActionSelectAll->setEnabled( false );
|
|
mActionInvertSelection->setEnabled( false );
|
|
mActionOpenFieldCalc->setEnabled( false );
|
|
mActionToggleEditing->setEnabled( false );
|
|
mActionToggleEditing->setChecked( false );
|
|
mActionSaveLayerEdits->setEnabled( false );
|
|
mActionSaveLayerDefinition->setEnabled( false );
|
|
mActionLayerSaveAs->setEnabled( false );
|
|
mActionLayerProperties->setEnabled( false );
|
|
mActionLayerSubsetString->setEnabled( false );
|
|
mActionAddToOverview->setEnabled( false );
|
|
mActionFeatureAction->setEnabled( false );
|
|
mActionAddFeature->setEnabled( false );
|
|
mActionCircularStringCurvePoint->setEnabled( false );
|
|
mActionCircularStringRadius->setEnabled( false );
|
|
mActionCircle2Points->setEnabled( false );
|
|
mActionCircle3Points->setEnabled( false );
|
|
mActionCircle3Tangents->setEnabled( false );
|
|
mActionCircle2TangentsPoint->setEnabled( false );
|
|
mActionCircleCenterPoint->setEnabled( false );
|
|
mActionEllipseCenter2Points->setEnabled( false );
|
|
mActionEllipseCenterPoint->setEnabled( false );
|
|
mActionEllipseExtent->setEnabled( false );
|
|
mActionEllipseFoci->setEnabled( false );
|
|
mActionRectangleCenterPoint->setEnabled( false );
|
|
mActionRectangleExtent->setEnabled( false );
|
|
mActionRegularPolygon2Points->setEnabled( false );
|
|
mActionRegularPolygonCenterPoint->setEnabled( false );
|
|
mActionRegularPolygonCenterCorner->setEnabled( false );
|
|
mActionMoveFeature->setEnabled( false );
|
|
mActionMoveFeatureCopy->setEnabled( false );
|
|
mActionRotateFeature->setEnabled( false );
|
|
mActionOffsetCurve->setEnabled( false );
|
|
mActionNodeTool->setEnabled( false );
|
|
mActionDeleteSelected->setEnabled( false );
|
|
mActionCutFeatures->setEnabled( false );
|
|
mActionCopyFeatures->setEnabled( false );
|
|
mActionPasteFeatures->setEnabled( false );
|
|
mActionCopyStyle->setEnabled( false );
|
|
mActionPasteStyle->setEnabled( false );
|
|
|
|
mUndoDock->widget()->setEnabled( false );
|
|
mActionUndo->setEnabled( false );
|
|
mActionRedo->setEnabled( false );
|
|
mActionSimplifyFeature->setEnabled( false );
|
|
mActionAddRing->setEnabled( false );
|
|
mActionFillRing->setEnabled( false );
|
|
mActionAddPart->setEnabled( false );
|
|
mActionDeleteRing->setEnabled( false );
|
|
mActionDeletePart->setEnabled( false );
|
|
mActionReshapeFeatures->setEnabled( false );
|
|
mActionOffsetCurve->setEnabled( false );
|
|
mActionSplitFeatures->setEnabled( false );
|
|
mActionSplitParts->setEnabled( false );
|
|
mActionMergeFeatures->setEnabled( false );
|
|
mActionMergeFeatureAttributes->setEnabled( false );
|
|
mActionMultiEditAttributes->setEnabled( false );
|
|
mActionRotatePointSymbols->setEnabled( false );
|
|
mActionOffsetPointSymbol->setEnabled( false );
|
|
|
|
mActionPinLabels->setEnabled( false );
|
|
mActionShowHideLabels->setEnabled( false );
|
|
mActionMoveLabel->setEnabled( false );
|
|
mActionRotateLabel->setEnabled( false );
|
|
mActionChangeLabelProperties->setEnabled( false );
|
|
|
|
mActionDiagramProperties->setEnabled( false );
|
|
|
|
mActionLocalHistogramStretch->setEnabled( false );
|
|
mActionFullHistogramStretch->setEnabled( false );
|
|
mActionLocalCumulativeCutStretch->setEnabled( false );
|
|
mActionFullCumulativeCutStretch->setEnabled( false );
|
|
mActionIncreaseBrightness->setEnabled( false );
|
|
mActionDecreaseBrightness->setEnabled( false );
|
|
mActionIncreaseContrast->setEnabled( false );
|
|
mActionDecreaseContrast->setEnabled( false );
|
|
mActionZoomActualSize->setEnabled( false );
|
|
mActionZoomToLayer->setEnabled( false );
|
|
return;
|
|
}
|
|
|
|
mActionLayerProperties->setEnabled( QgsProject::instance()->layerIsEmbedded( layer->id() ).isEmpty() );
|
|
mActionAddToOverview->setEnabled( true );
|
|
mActionZoomToLayer->setEnabled( true );
|
|
|
|
mActionCopyStyle->setEnabled( true );
|
|
mActionPasteStyle->setEnabled( clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) );
|
|
|
|
/***********Vector layers****************/
|
|
if ( layer->type() == QgsMapLayer::VectorLayer )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
|
|
QgsVectorDataProvider *dprovider = vlayer->dataProvider();
|
|
QString addFeatureText;
|
|
|
|
bool isEditable = vlayer->isEditable();
|
|
bool layerHasSelection = vlayer->selectedFeatureCount() > 0;
|
|
bool layerHasActions = !vlayer->actions()->actions( QStringLiteral( "Canvas" ) ).isEmpty() || !QgsGui::mapLayerActionRegistry()->mapLayerActions( vlayer ).isEmpty();
|
|
bool isSpatial = vlayer->isSpatial();
|
|
|
|
mActionLocalHistogramStretch->setEnabled( false );
|
|
mActionFullHistogramStretch->setEnabled( false );
|
|
mActionLocalCumulativeCutStretch->setEnabled( false );
|
|
mActionFullCumulativeCutStretch->setEnabled( false );
|
|
mActionIncreaseBrightness->setEnabled( false );
|
|
mActionDecreaseBrightness->setEnabled( false );
|
|
mActionIncreaseContrast->setEnabled( false );
|
|
mActionDecreaseContrast->setEnabled( false );
|
|
mActionZoomActualSize->setEnabled( false );
|
|
mActionZoomToLayer->setEnabled( isSpatial );
|
|
mActionZoomToSelected->setEnabled( isSpatial );
|
|
mActionLabeling->setEnabled( isSpatial );
|
|
mActionDiagramProperties->setEnabled( isSpatial );
|
|
|
|
mActionSelectFeatures->setEnabled( isSpatial );
|
|
mActionSelectPolygon->setEnabled( isSpatial );
|
|
mActionSelectFreehand->setEnabled( isSpatial );
|
|
mActionSelectRadius->setEnabled( isSpatial );
|
|
mActionIdentify->setEnabled( isSpatial );
|
|
mActionSelectByExpression->setEnabled( true );
|
|
mActionSelectByForm->setEnabled( true );
|
|
mActionOpenTable->setEnabled( true );
|
|
mActionSelectAll->setEnabled( true );
|
|
mActionInvertSelection->setEnabled( true );
|
|
mActionSaveLayerDefinition->setEnabled( true );
|
|
mActionLayerSaveAs->setEnabled( true );
|
|
mActionCopyFeatures->setEnabled( layerHasSelection );
|
|
mActionFeatureAction->setEnabled( layerHasActions );
|
|
|
|
if ( !isEditable && mMapCanvas && mMapCanvas->mapTool()
|
|
&& ( mMapCanvas->mapTool()->flags() & QgsMapTool::EditTool ) && !mSaveRollbackInProgress )
|
|
{
|
|
mMapCanvas->setMapTool( mNonEditMapTool );
|
|
}
|
|
|
|
if ( dprovider )
|
|
{
|
|
bool canChangeAttributes = dprovider->capabilities() & QgsVectorDataProvider::ChangeAttributeValues;
|
|
bool canDeleteFeatures = dprovider->capabilities() & QgsVectorDataProvider::DeleteFeatures;
|
|
bool canAddFeatures = dprovider->capabilities() & QgsVectorDataProvider::AddFeatures;
|
|
bool canSupportEditing = dprovider->capabilities() & QgsVectorDataProvider::EditingCapabilities;
|
|
bool canChangeGeometry = isSpatial && dprovider->capabilities() & QgsVectorDataProvider::ChangeGeometries;
|
|
|
|
mActionLayerSubsetString->setEnabled( !isEditable && dprovider->supportsSubsetString() );
|
|
|
|
mActionToggleEditing->setEnabled( canSupportEditing && !vlayer->readOnly() );
|
|
mActionToggleEditing->setChecked( canSupportEditing && isEditable );
|
|
mActionSaveLayerEdits->setEnabled( canSupportEditing && isEditable && vlayer->isModified() );
|
|
mUndoDock->widget()->setEnabled( canSupportEditing && isEditable );
|
|
mActionUndo->setEnabled( canSupportEditing );
|
|
mActionRedo->setEnabled( canSupportEditing );
|
|
|
|
//start editing/stop editing
|
|
if ( canSupportEditing )
|
|
{
|
|
updateUndoActions();
|
|
}
|
|
|
|
mActionPasteFeatures->setEnabled( isEditable && canAddFeatures && !clipboard()->isEmpty() );
|
|
|
|
mActionAddFeature->setEnabled( isEditable && canAddFeatures );
|
|
|
|
bool enableCircularTools;
|
|
bool enableShapeTools;
|
|
enableCircularTools = isEditable && ( canAddFeatures || canChangeGeometry )
|
|
&& ( vlayer->geometryType() == QgsWkbTypes::LineGeometry || vlayer->geometryType() == QgsWkbTypes::PolygonGeometry );
|
|
enableShapeTools = enableCircularTools;
|
|
mActionCircularStringCurvePoint->setEnabled( enableCircularTools );
|
|
mActionCircularStringRadius->setEnabled( enableCircularTools );
|
|
mActionCircle2Points->setEnabled( enableShapeTools );
|
|
mActionCircle3Points->setEnabled( enableShapeTools );
|
|
mActionCircle3Tangents->setEnabled( enableShapeTools );
|
|
mActionCircle2TangentsPoint->setEnabled( enableShapeTools );
|
|
mActionCircleCenterPoint->setEnabled( enableShapeTools );
|
|
mActionEllipseCenter2Points->setEnabled( enableShapeTools );
|
|
mActionEllipseCenterPoint->setEnabled( enableShapeTools );
|
|
mActionEllipseExtent->setEnabled( enableShapeTools );
|
|
mActionEllipseFoci->setEnabled( enableShapeTools );
|
|
mActionRectangleCenterPoint->setEnabled( enableShapeTools );
|
|
mActionRectangleExtent->setEnabled( enableShapeTools );
|
|
mActionRectangle3Points->setEnabled( enableShapeTools );
|
|
mActionRegularPolygon2Points->setEnabled( enableShapeTools );
|
|
mActionRegularPolygonCenterPoint->setEnabled( enableShapeTools );
|
|
mActionRegularPolygonCenterCorner->setEnabled( enableShapeTools );
|
|
|
|
//does provider allow deleting of features?
|
|
mActionDeleteSelected->setEnabled( isEditable && canDeleteFeatures && layerHasSelection );
|
|
mActionCutFeatures->setEnabled( isEditable && canDeleteFeatures && layerHasSelection );
|
|
|
|
//merge tool needs editable layer and provider with the capability of adding and deleting features
|
|
if ( isEditable && canChangeAttributes )
|
|
{
|
|
mActionMergeFeatures->setEnabled( layerHasSelection && canDeleteFeatures && canAddFeatures );
|
|
mActionMergeFeatureAttributes->setEnabled( layerHasSelection );
|
|
mActionMultiEditAttributes->setEnabled( layerHasSelection );
|
|
}
|
|
else
|
|
{
|
|
mActionMergeFeatures->setEnabled( false );
|
|
mActionMergeFeatureAttributes->setEnabled( false );
|
|
mActionMultiEditAttributes->setEnabled( false );
|
|
}
|
|
|
|
bool isMultiPart = QgsWkbTypes::isMultiType( vlayer->wkbType() ) || !dprovider->doesStrictFeatureTypeCheck();
|
|
|
|
// moving enabled if geometry changes are supported
|
|
mActionAddPart->setEnabled( isEditable && canChangeGeometry );
|
|
mActionDeletePart->setEnabled( isEditable && canChangeGeometry );
|
|
mActionMoveFeature->setEnabled( isEditable && canChangeGeometry );
|
|
mActionMoveFeatureCopy->setEnabled( isEditable && canChangeGeometry );
|
|
mActionRotateFeature->setEnabled( isEditable && canChangeGeometry );
|
|
mActionNodeTool->setEnabled( isEditable && canChangeGeometry );
|
|
|
|
if ( vlayer->geometryType() == QgsWkbTypes::PointGeometry )
|
|
{
|
|
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePoint.svg" ) ) );
|
|
addFeatureText = tr( "Add Point Feature" );
|
|
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeaturePoint.svg" ) ) );
|
|
mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyPoint.svg" ) ) );
|
|
|
|
mActionAddRing->setEnabled( false );
|
|
mActionFillRing->setEnabled( false );
|
|
mActionReshapeFeatures->setEnabled( false );
|
|
mActionSplitFeatures->setEnabled( false );
|
|
mActionSplitParts->setEnabled( false );
|
|
mActionSimplifyFeature->setEnabled( false );
|
|
mActionDeleteRing->setEnabled( false );
|
|
mActionRotatePointSymbols->setEnabled( false );
|
|
mActionOffsetPointSymbol->setEnabled( false );
|
|
mActionOffsetCurve->setEnabled( false );
|
|
|
|
if ( isEditable && canChangeAttributes )
|
|
{
|
|
if ( QgsMapToolRotatePointSymbols::layerIsRotatable( vlayer ) )
|
|
{
|
|
mActionRotatePointSymbols->setEnabled( true );
|
|
}
|
|
if ( QgsMapToolOffsetPointSymbol::layerIsOffsetable( vlayer ) )
|
|
{
|
|
mActionOffsetPointSymbol->setEnabled( true );
|
|
}
|
|
}
|
|
}
|
|
else if ( vlayer->geometryType() == QgsWkbTypes::LineGeometry )
|
|
{
|
|
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCaptureLine.svg" ) ) );
|
|
addFeatureText = tr( "Add Line Feature" );
|
|
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureLine.svg" ) ) );
|
|
mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopyLine.svg" ) ) );
|
|
|
|
mActionReshapeFeatures->setEnabled( isEditable && canChangeGeometry );
|
|
mActionSplitFeatures->setEnabled( isEditable && canAddFeatures );
|
|
mActionSplitParts->setEnabled( isEditable && canChangeGeometry && isMultiPart );
|
|
mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
|
|
mActionOffsetCurve->setEnabled( isEditable && canAddFeatures && canChangeAttributes );
|
|
|
|
mActionAddRing->setEnabled( false );
|
|
mActionFillRing->setEnabled( false );
|
|
mActionDeleteRing->setEnabled( false );
|
|
}
|
|
else if ( vlayer->geometryType() == QgsWkbTypes::PolygonGeometry )
|
|
{
|
|
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCapturePolygon.svg" ) ) );
|
|
addFeatureText = tr( "Add Polygon Feature" );
|
|
mActionMoveFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeature.svg" ) ) );
|
|
mActionMoveFeatureCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMoveFeatureCopy.svg" ) ) );
|
|
|
|
mActionAddRing->setEnabled( isEditable && canChangeGeometry );
|
|
mActionFillRing->setEnabled( isEditable && canChangeGeometry );
|
|
mActionReshapeFeatures->setEnabled( isEditable && canChangeGeometry );
|
|
mActionSplitFeatures->setEnabled( isEditable && canAddFeatures );
|
|
mActionSplitParts->setEnabled( isEditable && canChangeGeometry && isMultiPart );
|
|
mActionSimplifyFeature->setEnabled( isEditable && canChangeGeometry );
|
|
mActionDeleteRing->setEnabled( isEditable && canChangeGeometry );
|
|
mActionOffsetCurve->setEnabled( false );
|
|
}
|
|
else if ( vlayer->geometryType() == QgsWkbTypes::NullGeometry )
|
|
{
|
|
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewTableRow.svg" ) ) );
|
|
addFeatureText = tr( "Add Record" );
|
|
mActionAddRing->setEnabled( false );
|
|
mActionFillRing->setEnabled( false );
|
|
mActionReshapeFeatures->setEnabled( false );
|
|
mActionSplitFeatures->setEnabled( false );
|
|
mActionSplitParts->setEnabled( false );
|
|
mActionSimplifyFeature->setEnabled( false );
|
|
mActionDeleteRing->setEnabled( false );
|
|
mActionOffsetCurve->setEnabled( false );
|
|
}
|
|
|
|
mActionOpenFieldCalc->setEnabled( true );
|
|
mActionAddFeature->setText( addFeatureText );
|
|
mActionAddFeature->setToolTip( addFeatureText );
|
|
QgsGui::shortcutsManager()->unregisterAction( mActionAddFeature );
|
|
QgsGui::shortcutsManager()->registerAction( mActionAddFeature, mActionAddFeature->shortcut() );
|
|
}
|
|
else
|
|
{
|
|
mUndoDock->widget()->setEnabled( false );
|
|
mActionUndo->setEnabled( false );
|
|
mActionRedo->setEnabled( false );
|
|
mActionLayerSubsetString->setEnabled( false );
|
|
}
|
|
} //end vector layer block
|
|
/*************Raster layers*************/
|
|
else if ( layer->type() == QgsMapLayer::RasterLayer )
|
|
{
|
|
const QgsRasterLayer *rlayer = qobject_cast<const QgsRasterLayer *>( layer );
|
|
if ( rlayer->dataProvider()->dataType( 1 ) != Qgis::ARGB32
|
|
&& rlayer->dataProvider()->dataType( 1 ) != Qgis::ARGB32_Premultiplied )
|
|
{
|
|
if ( rlayer->dataProvider()->capabilities() & QgsRasterDataProvider::Size )
|
|
{
|
|
mActionFullHistogramStretch->setEnabled( true );
|
|
}
|
|
else
|
|
{
|
|
// it would hang up reading the data for WMS for example
|
|
mActionFullHistogramStretch->setEnabled( false );
|
|
}
|
|
mActionLocalHistogramStretch->setEnabled( true );
|
|
}
|
|
else
|
|
{
|
|
mActionLocalHistogramStretch->setEnabled( false );
|
|
mActionFullHistogramStretch->setEnabled( false );
|
|
}
|
|
|
|
mActionLocalCumulativeCutStretch->setEnabled( true );
|
|
mActionFullCumulativeCutStretch->setEnabled( true );
|
|
mActionIncreaseBrightness->setEnabled( true );
|
|
mActionDecreaseBrightness->setEnabled( true );
|
|
mActionIncreaseContrast->setEnabled( true );
|
|
mActionDecreaseContrast->setEnabled( true );
|
|
|
|
mActionLayerSubsetString->setEnabled( false );
|
|
mActionFeatureAction->setEnabled( false );
|
|
mActionSelectFeatures->setEnabled( false );
|
|
mActionSelectPolygon->setEnabled( false );
|
|
mActionSelectFreehand->setEnabled( false );
|
|
mActionSelectRadius->setEnabled( false );
|
|
mActionZoomActualSize->setEnabled( true );
|
|
mActionZoomToLayer->setEnabled( true );
|
|
mActionZoomToSelected->setEnabled( false );
|
|
mActionOpenTable->setEnabled( false );
|
|
mActionSelectAll->setEnabled( false );
|
|
mActionInvertSelection->setEnabled( false );
|
|
mActionSelectByExpression->setEnabled( false );
|
|
mActionSelectByForm->setEnabled( false );
|
|
mActionOpenFieldCalc->setEnabled( false );
|
|
mActionToggleEditing->setEnabled( false );
|
|
mActionToggleEditing->setChecked( false );
|
|
mActionSaveLayerEdits->setEnabled( false );
|
|
mUndoDock->widget()->setEnabled( false );
|
|
mActionUndo->setEnabled( false );
|
|
mActionRedo->setEnabled( false );
|
|
mActionSaveLayerDefinition->setEnabled( true );
|
|
mActionLayerSaveAs->setEnabled( true );
|
|
mActionAddFeature->setEnabled( false );
|
|
mActionCircularStringCurvePoint->setEnabled( false );
|
|
mActionCircularStringRadius->setEnabled( false );
|
|
mActionDeleteSelected->setEnabled( false );
|
|
mActionAddRing->setEnabled( false );
|
|
mActionFillRing->setEnabled( false );
|
|
mActionAddPart->setEnabled( false );
|
|
mActionNodeTool->setEnabled( false );
|
|
mActionMoveFeature->setEnabled( false );
|
|
mActionMoveFeatureCopy->setEnabled( false );
|
|
mActionRotateFeature->setEnabled( false );
|
|
mActionOffsetCurve->setEnabled( false );
|
|
mActionCopyFeatures->setEnabled( false );
|
|
mActionCutFeatures->setEnabled( false );
|
|
mActionPasteFeatures->setEnabled( false );
|
|
mActionRotatePointSymbols->setEnabled( false );
|
|
mActionOffsetPointSymbol->setEnabled( false );
|
|
mActionDeletePart->setEnabled( false );
|
|
mActionDeleteRing->setEnabled( false );
|
|
mActionSimplifyFeature->setEnabled( false );
|
|
mActionReshapeFeatures->setEnabled( false );
|
|
mActionSplitFeatures->setEnabled( false );
|
|
mActionSplitParts->setEnabled( false );
|
|
mActionLabeling->setEnabled( false );
|
|
mActionDiagramProperties->setEnabled( false );
|
|
|
|
//NOTE: This check does not really add any protection, as it is called on load not on layer select/activate
|
|
//If you load a layer with a provider and idenitfy ability then load another without, the tool would be disabled for both
|
|
|
|
//Enable the Identify tool ( GDAL datasets draw without a provider )
|
|
//but turn off if data provider exists and has no Identify capabilities
|
|
mActionIdentify->setEnabled( true );
|
|
|
|
QgsSettings settings;
|
|
int identifyMode = settings.value( QStringLiteral( "Map/identifyMode" ), 0 ).toInt();
|
|
if ( identifyMode == 0 )
|
|
{
|
|
const QgsRasterLayer *rlayer = qobject_cast<const QgsRasterLayer *>( layer );
|
|
const QgsRasterDataProvider *dprovider = rlayer->dataProvider();
|
|
if ( dprovider )
|
|
{
|
|
// does provider allow the identify map tool?
|
|
if ( dprovider->capabilities() & QgsRasterDataProvider::Identify )
|
|
{
|
|
mActionIdentify->setEnabled( true );
|
|
}
|
|
else
|
|
{
|
|
mActionIdentify->setEnabled( false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
refreshFeatureActions();
|
|
}
|
|
|
|
void QgisApp::refreshActionFeatureAction()
|
|
{
|
|
mActionFeatureAction->setEnabled( false );
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
|
|
if ( !vlayer )
|
|
return;
|
|
|
|
bool layerHasActions = !vlayer->actions()->actions( QStringLiteral( "Canvas" ) ).isEmpty() || !QgsGui::mapLayerActionRegistry()->mapLayerActions( vlayer ).isEmpty();
|
|
mActionFeatureAction->setEnabled( layerHasActions );
|
|
}
|
|
|
|
void QgisApp::renameView()
|
|
{
|
|
QgsMapCanvasDockWidget *view = qobject_cast< QgsMapCanvasDockWidget * >( sender() );
|
|
if ( !view )
|
|
return;
|
|
|
|
// calculate existing names
|
|
QStringList names;
|
|
Q_FOREACH ( QgsMapCanvas *c, mapCanvases() )
|
|
{
|
|
if ( c == view->mapCanvas() )
|
|
continue;
|
|
|
|
names << c->objectName();
|
|
}
|
|
|
|
QString currentName = view->mapCanvas()->objectName();
|
|
|
|
QgsNewNameDialog renameDlg( currentName, currentName, QStringList(), names, QRegExp(), Qt::CaseSensitive, this );
|
|
renameDlg.setWindowTitle( tr( "Map Views" ) );
|
|
//renameDlg.setHintString( tr( "Name of the new view" ) );
|
|
renameDlg.setOverwriteEnabled( false );
|
|
renameDlg.setConflictingNameWarning( tr( "A view with this name already exists" ) );
|
|
if ( renameDlg.exec() || renameDlg.name().isEmpty() )
|
|
{
|
|
QString newName = renameDlg.name();
|
|
view->setWindowTitle( newName );
|
|
view->mapCanvas()->setObjectName( newName );
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
// Only functions relating to raster layer management in this
|
|
// section (look for a similar comment block to this to find
|
|
// the end of this section).
|
|
//
|
|
// Tim Sutton
|
|
//
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// this is a slot for action from GUI to open and add raster layers
|
|
void QgisApp::addRasterLayer()
|
|
{
|
|
QStringList selectedFiles;
|
|
QString e;//only for parameter correctness
|
|
QString title = tr( "Open a GDAL Supported Raster Data Source" );
|
|
QgsGuiUtils::openFilesRememberingFilter( QStringLiteral( "lastRasterFileFilter" ), mRasterFileFilter, selectedFiles, e,
|
|
title );
|
|
|
|
if ( selectedFiles.isEmpty() )
|
|
{
|
|
// no files were selected, so just bail
|
|
return;
|
|
}
|
|
|
|
addRasterLayers( selectedFiles );
|
|
|
|
}
|
|
|
|
//
|
|
// This is the method that does the actual work of adding a raster layer - the others
|
|
// here simply create a raster layer object and delegate here. It is the responsibility
|
|
// of the calling method to manage things such as the frozen state of the mapcanvas and
|
|
// using waitcursors etc. - this method won't and SHOULDN'T do it
|
|
//
|
|
bool QgisApp::addRasterLayer( QgsRasterLayer *rasterLayer )
|
|
{
|
|
Q_CHECK_PTR( rasterLayer );
|
|
|
|
if ( ! rasterLayer )
|
|
{
|
|
// XXX insert meaningful whine to the user here; although be
|
|
// XXX mindful that a null layer may mean exhausted memory resources
|
|
return false;
|
|
}
|
|
|
|
if ( !rasterLayer->isValid() )
|
|
{
|
|
delete rasterLayer;
|
|
return false;
|
|
}
|
|
|
|
// register this layer with the central layers registry
|
|
QList<QgsMapLayer *> myList;
|
|
myList << rasterLayer;
|
|
QgsProject::instance()->addMapLayers( myList );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Open a raster layer - this is the generic function which takes all parameters
|
|
// this method is a blend of addRasterLayer() functions (with and without provider)
|
|
// and addRasterLayers()
|
|
QgsRasterLayer *QgisApp::addRasterLayerPrivate(
|
|
const QString &uri, const QString &baseName, const QString &providerKey,
|
|
bool guiWarning, bool guiUpdate )
|
|
{
|
|
if ( guiUpdate )
|
|
{
|
|
// let the user know we're going to possibly be taking a while
|
|
// QApplication::setOverrideCursor( Qt::WaitCursor );
|
|
freezeCanvases();
|
|
}
|
|
|
|
QgsDebugMsg( "Creating new raster layer using " + uri
|
|
+ " with baseName of " + baseName );
|
|
|
|
QgsRasterLayer *layer = nullptr;
|
|
// XXX ya know QgsRasterLayer can snip out the basename on its own;
|
|
// XXX why do we have to pass it in for it?
|
|
// ET : we may not be getting "normal" files here, so we still need the baseName argument
|
|
if ( !providerKey.isEmpty() && uri.endsWith( QLatin1String( ".adf" ), Qt::CaseInsensitive ) )
|
|
{
|
|
QFileInfo fileInfo( uri );
|
|
QString dirName = fileInfo.path();
|
|
layer = new QgsRasterLayer( dirName, QFileInfo( dirName ).completeBaseName(), QStringLiteral( "gdal" ) );
|
|
}
|
|
else if ( providerKey.isEmpty() )
|
|
layer = new QgsRasterLayer( uri, baseName ); // fi.completeBaseName());
|
|
else
|
|
layer = new QgsRasterLayer( uri, baseName, providerKey );
|
|
|
|
QgsDebugMsg( "Constructed new layer" );
|
|
|
|
QgsError error;
|
|
QString title;
|
|
bool ok = false;
|
|
|
|
if ( !layer->isValid() )
|
|
{
|
|
error = layer->error();
|
|
title = tr( "Invalid Layer" );
|
|
|
|
if ( shouldAskUserForGDALSublayers( layer ) )
|
|
{
|
|
askUserForGDALSublayers( layer );
|
|
ok = true;
|
|
|
|
// The first layer loaded is not useful in that case. The user can select it in
|
|
// the list if he wants to load it.
|
|
delete layer;
|
|
layer = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ok = addRasterLayer( layer );
|
|
if ( !ok )
|
|
{
|
|
error.append( QGS_ERROR_MESSAGE( tr( "Error adding valid layer to map canvas" ),
|
|
tr( "Raster layer" ) ) );
|
|
title = tr( "Error" );
|
|
}
|
|
}
|
|
|
|
if ( !ok )
|
|
{
|
|
if ( guiUpdate )
|
|
freezeCanvases( false );
|
|
|
|
// don't show the gui warning if we are loading from command line
|
|
if ( guiWarning )
|
|
{
|
|
messageBar()->pushMessage( title, error.message( QgsErrorMessage::Text ),
|
|
QgsMessageBar::CRITICAL, messageTimeout() );
|
|
}
|
|
|
|
if ( layer )
|
|
{
|
|
delete layer;
|
|
layer = nullptr;
|
|
}
|
|
}
|
|
|
|
if ( guiUpdate )
|
|
{
|
|
// draw the map
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
// Let render() do its own cursor management
|
|
// QApplication::restoreOverrideCursor();
|
|
}
|
|
|
|
return layer;
|
|
|
|
} // QgisApp::addRasterLayer
|
|
|
|
|
|
//create a raster layer object and delegate to addRasterLayer(QgsRasterLayer *)
|
|
QgsRasterLayer *QgisApp::addRasterLayer(
|
|
QString const &rasterFile, QString const &baseName, bool guiWarning )
|
|
{
|
|
return addRasterLayerPrivate( rasterFile, baseName, QString(), guiWarning, true );
|
|
}
|
|
|
|
|
|
QgsRasterLayer *QgisApp::addRasterLayer(
|
|
QString const &uri, QString const &baseName, QString const &providerKey )
|
|
{
|
|
return addRasterLayerPrivate( uri, baseName, providerKey, true, true );
|
|
}
|
|
|
|
|
|
//create a raster layer object and delegate to addRasterLayer(QgsRasterLayer *)
|
|
bool QgisApp::addRasterLayers( QStringList const &fileNameQStringList, bool guiWarning )
|
|
{
|
|
if ( fileNameQStringList.empty() )
|
|
{
|
|
// no files selected so bail out, but
|
|
// allow mMapCanvas to handle events
|
|
// first
|
|
freezeCanvases( false );;
|
|
return false;
|
|
}
|
|
|
|
freezeCanvases();
|
|
|
|
// this is messy since some files in the list may be rasters and others may
|
|
// be ogr layers. We'll set returnValue to false if one or more layers fail
|
|
// to load.
|
|
bool returnValue = true;
|
|
for ( QStringList::ConstIterator myIterator = fileNameQStringList.begin();
|
|
myIterator != fileNameQStringList.end();
|
|
++myIterator )
|
|
{
|
|
QString errMsg;
|
|
bool ok = false;
|
|
|
|
// if needed prompt for zipitem layers
|
|
QString vsiPrefix = QgsZipItem::vsiPrefix( *myIterator );
|
|
if ( ! myIterator->startsWith( QLatin1String( "/vsi" ), Qt::CaseInsensitive ) &&
|
|
( vsiPrefix == QLatin1String( "/vsizip/" ) || vsiPrefix == QLatin1String( "/vsitar/" ) ) )
|
|
{
|
|
if ( askUserForZipItemLayers( *myIterator ) )
|
|
continue;
|
|
}
|
|
|
|
if ( QgsRasterLayer::isValidRasterFileName( *myIterator, errMsg ) )
|
|
{
|
|
QFileInfo myFileInfo( *myIterator );
|
|
|
|
// try to create the layer
|
|
QgsRasterLayer *layer = addRasterLayerPrivate( *myIterator, myFileInfo.completeBaseName(),
|
|
QString(), guiWarning, true );
|
|
if ( layer && layer->isValid() )
|
|
{
|
|
//only allow one copy of a ai grid file to be loaded at a
|
|
//time to prevent the user selecting all adfs in 1 dir which
|
|
//actually represent 1 coverate,
|
|
|
|
if ( myFileInfo.fileName().toLower().endsWith( QLatin1String( ".adf" ) ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// if layer is invalid addRasterLayerPrivate() will show the error
|
|
|
|
} // valid raster filename
|
|
else
|
|
{
|
|
ok = false;
|
|
|
|
// Issue message box warning unless we are loading from cmd line since
|
|
// non-rasters are passed to this function first and then successfully
|
|
// loaded afterwards (see main.cpp)
|
|
if ( guiWarning )
|
|
{
|
|
QString msg = tr( "%1 is not a supported raster data source" ).arg( *myIterator );
|
|
if ( !errMsg.isEmpty() )
|
|
msg += '\n' + errMsg;
|
|
|
|
messageBar()->pushMessage( tr( "Unsupported Data Source" ), msg, QgsMessageBar::CRITICAL, messageTimeout() );
|
|
}
|
|
}
|
|
if ( ! ok )
|
|
{
|
|
returnValue = false;
|
|
}
|
|
}
|
|
|
|
freezeCanvases( false );
|
|
refreshMapCanvas();
|
|
|
|
// Let render() do its own cursor management
|
|
// QApplication::restoreOverrideCursor();
|
|
|
|
return returnValue;
|
|
|
|
}// QgisApp::addRasterLayer()
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
//
|
|
//
|
|
// RASTER ONLY RELATED FUNCTIONS BLOCK ENDS....
|
|
//
|
|
//
|
|
//
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
|
|
QgsPluginLayer *QgisApp::addPluginLayer( const QString &uri, const QString &baseName, const QString &providerKey )
|
|
{
|
|
QgsPluginLayer *layer = QgsApplication::pluginLayerRegistry()->createLayer( providerKey, uri );
|
|
if ( !layer )
|
|
return nullptr;
|
|
|
|
layer->setName( baseName );
|
|
|
|
QgsProject::instance()->addMapLayer( layer );
|
|
|
|
return layer;
|
|
}
|
|
|
|
|
|
|
|
#ifdef ANDROID
|
|
void QgisApp::keyReleaseEvent( QKeyEvent *event )
|
|
{
|
|
static bool sAccepted = true;
|
|
if ( event->key() == Qt::Key_Close )
|
|
{
|
|
// do something useful here
|
|
int ret = QMessageBox::question( this, tr( "Exit QGIS" ),
|
|
tr( "Do you really want to quit QGIS?" ),
|
|
QMessageBox::Yes | QMessageBox::No );
|
|
switch ( ret )
|
|
{
|
|
case QMessageBox::Yes:
|
|
this->close();
|
|
break;
|
|
|
|
case QMessageBox::No:
|
|
break;
|
|
}
|
|
event->setAccepted( sAccepted ); // don't close my Top Level Widget !
|
|
sAccepted = false;// close the app next time when the user press back button
|
|
}
|
|
else
|
|
{
|
|
QMainWindow::keyReleaseEvent( event );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void QgisApp::keyPressEvent( QKeyEvent *e )
|
|
{
|
|
// The following statement causes a crash on WIN32 and should be
|
|
// enclosed in an #ifdef QGISDEBUG if its really necessary. Its
|
|
// commented out for now. [gsherman]
|
|
// QgsDebugMsg( QString( "%1 (keypress received)" ).arg( e->text() ) );
|
|
emit keyPressed( e );
|
|
|
|
//cancel rendering progress with esc key
|
|
if ( e->key() == Qt::Key_Escape )
|
|
{
|
|
stopRendering();
|
|
}
|
|
#if defined(Q_OS_WIN) && defined(QGISDEBUG)
|
|
else if ( e->key() == Qt::Key_Backslash && e->modifiers() & Qt::ControlModifier )
|
|
{
|
|
QgsCrashHandler::handle( 0 );
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
e->ignore();
|
|
}
|
|
}
|
|
|
|
void QgisApp::newProfile()
|
|
{
|
|
QString text = QInputDialog::getText( this, tr( "New profile name" ), tr( "New profile name" ) );
|
|
if ( text.isEmpty() )
|
|
return;
|
|
|
|
userProfileManager()->createUserProfile( text );
|
|
userProfileManager()->loadUserProfile( text );
|
|
}
|
|
|
|
void QgisApp::onTaskCompleteShowNotify( long taskId, int status )
|
|
{
|
|
if ( status == QgsTask::Complete && !this->isActiveWindow() )
|
|
{
|
|
QgsTask *task = QgsApplication::taskManager()->task( taskId );
|
|
if ( task )
|
|
{
|
|
showSystemNotification( tr( "Task complete" ), task->description() );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::onTransactionGroupsChanged()
|
|
{
|
|
const auto groups = QgsProject::instance()->transactionGroups();
|
|
for ( auto it = groups.constBegin(); it != groups.constEnd(); ++it )
|
|
{
|
|
connect( it.value(), &QgsTransactionGroup::commitError, this, &QgisApp::transactionGroupCommitError, Qt::UniqueConnection );
|
|
}
|
|
}
|
|
|
|
void QgisApp::onSnappingConfigChanged()
|
|
{
|
|
mSnappingUtils->setConfig( QgsProject::instance()->snappingConfig() );
|
|
}
|
|
|
|
void QgisApp::startProfile( const QString &name )
|
|
{
|
|
QgsApplication::profiler()->start( name );
|
|
}
|
|
|
|
void QgisApp::endProfile()
|
|
{
|
|
QgsApplication::profiler()->end();
|
|
}
|
|
|
|
void QgisApp::functionProfile( void ( QgisApp::*fnc )(), QgisApp *instance, const QString &name )
|
|
{
|
|
startProfile( name );
|
|
( instance->*fnc )();
|
|
endProfile();
|
|
}
|
|
|
|
void QgisApp::mapCanvas_keyPressed( QKeyEvent *e )
|
|
{
|
|
// Delete selected features when it is possible and KeyEvent was not managed by current MapTool
|
|
if ( ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) && e->isAccepted() )
|
|
{
|
|
deleteSelected();
|
|
}
|
|
}
|
|
|
|
void QgisApp::customProjection()
|
|
{
|
|
// Create an instance of the Custom Projection Designer modeless dialog.
|
|
// Autodelete the dialog when closing since a pointer is not retained.
|
|
QgsCustomProjectionDialog *myDialog = new QgsCustomProjectionDialog( this );
|
|
myDialog->setAttribute( Qt::WA_DeleteOnClose );
|
|
myDialog->show();
|
|
}
|
|
|
|
void QgisApp::newBookmark()
|
|
{
|
|
showBookmarks();
|
|
mBookMarksDockWidget->addClicked();
|
|
}
|
|
|
|
void QgisApp::showBookmarks()
|
|
{
|
|
mBookMarksDockWidget->show();
|
|
mBookMarksDockWidget->raise();
|
|
}
|
|
|
|
// Slot that gets called when the project file was saved with an older
|
|
// version of QGIS
|
|
|
|
void QgisApp::oldProjectVersionWarning( const QString &oldVersion )
|
|
{
|
|
Q_UNUSED( oldVersion );
|
|
QgsSettings settings;
|
|
|
|
if ( settings.value( QStringLiteral( "qgis/warnOldProjectVersion" ), QVariant( true ) ).toBool() )
|
|
{
|
|
QString smalltext = tr( "This project file was saved by an older version of QGIS."
|
|
" When saving this project file, QGIS will update it to the latest version, "
|
|
"possibly rendering it useless for older versions of QGIS." );
|
|
|
|
QString title = tr( "Project file is older" );
|
|
|
|
messageBar()->pushMessage( title, smalltext );
|
|
}
|
|
}
|
|
|
|
void QgisApp::updateUndoActions()
|
|
{
|
|
bool canUndo = false, canRedo = false;
|
|
QgsMapLayer *layer = activeLayer();
|
|
if ( layer )
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
|
|
if ( vlayer && vlayer->isEditable() )
|
|
{
|
|
canUndo = vlayer->undoStack()->canUndo();
|
|
canRedo = vlayer->undoStack()->canRedo();
|
|
}
|
|
}
|
|
mActionUndo->setEnabled( canUndo );
|
|
mActionRedo->setEnabled( canRedo );
|
|
}
|
|
|
|
|
|
// add project directory to python path
|
|
void QgisApp::projectChanged( const QDomDocument &doc )
|
|
{
|
|
Q_UNUSED( doc );
|
|
QgsProject *project = qobject_cast<QgsProject *>( sender() );
|
|
if ( !project )
|
|
return;
|
|
|
|
QFileInfo fi( project->fileName() );
|
|
if ( !fi.exists() )
|
|
return;
|
|
|
|
static QString sPrevProjectDir = QString();
|
|
|
|
if ( sPrevProjectDir == fi.canonicalPath() )
|
|
return;
|
|
|
|
QString expr;
|
|
if ( !sPrevProjectDir.isNull() )
|
|
{
|
|
QString prev = sPrevProjectDir;
|
|
expr = QStringLiteral( "sys.path.remove(u'%1'); " ).arg( prev.replace( '\'', QLatin1String( "\\'" ) ) );
|
|
}
|
|
|
|
sPrevProjectDir = fi.canonicalPath();
|
|
|
|
QString prev = sPrevProjectDir;
|
|
expr += QStringLiteral( "sys.path.append(u'%1')" ).arg( prev.replace( '\'', QLatin1String( "\\'" ) ) );
|
|
|
|
QgsPythonRunner::run( expr );
|
|
}
|
|
|
|
void QgisApp::writeProject( QDomDocument &doc )
|
|
{
|
|
// QGIS server does not use QgsProject for loading of QGIS project.
|
|
// In order to allow reading of new projects, let's also write the original <legend> tag to the project.
|
|
// Ideally the server should be ported to new layer tree implementation, but that requires
|
|
// non-trivial changes to the server components.
|
|
// The <legend> tag is ignored by QGIS application in >= 2.4 and this way also the new project files
|
|
// can be opened in older versions of QGIS without losing information about layer groups.
|
|
|
|
QgsLayerTree *clonedRoot = QgsProject::instance()->layerTreeRoot()->clone();
|
|
QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) );
|
|
QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), QgsProject::instance() ); // convert absolute paths to relative paths if required
|
|
QDomElement oldLegendElem = QgsLayerTreeUtils::writeOldLegend( doc, QgsLayerTree::toGroup( clonedRoot ),
|
|
clonedRoot->hasCustomLayerOrder(), clonedRoot->customLayerOrder() );
|
|
delete clonedRoot;
|
|
QDomElement qgisNode = doc.firstChildElement( QStringLiteral( "qgis" ) );
|
|
qgisNode.appendChild( oldLegendElem );
|
|
|
|
QgsProject::instance()->writeEntry( QStringLiteral( "Legend" ), QStringLiteral( "filterByMap" ), static_cast< bool >( layerTreeView()->layerTreeModel()->legendFilterMapSettings() ) );
|
|
|
|
// Save the position of the map view docks
|
|
QDomElement mapViewNode = doc.createElement( QStringLiteral( "mapViewDocks" ) );
|
|
Q_FOREACH ( QgsMapCanvasDockWidget *w, findChildren< QgsMapCanvasDockWidget * >() )
|
|
{
|
|
QDomElement node = doc.createElement( QStringLiteral( "view" ) );
|
|
node.setAttribute( QStringLiteral( "name" ), w->mapCanvas()->objectName() );
|
|
node.setAttribute( QStringLiteral( "x" ), w->x() );
|
|
node.setAttribute( QStringLiteral( "y" ), w->y() );
|
|
node.setAttribute( QStringLiteral( "width" ), w->width() );
|
|
node.setAttribute( QStringLiteral( "height" ), w->height() );
|
|
node.setAttribute( QStringLiteral( "floating" ), w->isFloating() );
|
|
node.setAttribute( QStringLiteral( "area" ), dockWidgetArea( w ) );
|
|
node.setAttribute( QStringLiteral( "synced" ), w->isViewCenterSynchronized() );
|
|
node.setAttribute( QStringLiteral( "showCursor" ), w->isCursorMarkerVisible() );
|
|
node.setAttribute( QStringLiteral( "showExtent" ), w->isMainCanvasExtentVisible() );
|
|
node.setAttribute( QStringLiteral( "scaleSynced" ), w->isViewScaleSynchronized() );
|
|
node.setAttribute( QStringLiteral( "scaleFactor" ), w->scaleFactor() );
|
|
node.setAttribute( QStringLiteral( "showLabels" ), w->labelsVisible() );
|
|
mapViewNode.appendChild( node );
|
|
}
|
|
qgisNode.appendChild( mapViewNode );
|
|
|
|
projectChanged( doc );
|
|
}
|
|
|
|
void QgisApp::readProject( const QDomDocument &doc )
|
|
{
|
|
projectChanged( doc );
|
|
|
|
// force update of canvas, without automatic changes to extent and OTF projections
|
|
bool autoSetupOnFirstLayer = mLayerTreeCanvasBridge->autoSetupOnFirstLayer();
|
|
mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( false );
|
|
|
|
mLayerTreeCanvasBridge->setCanvasLayers();
|
|
|
|
if ( autoSetupOnFirstLayer )
|
|
mLayerTreeCanvasBridge->setAutoSetupOnFirstLayer( true );
|
|
|
|
QDomNodeList nodes = doc.elementsByTagName( QStringLiteral( "mapViewDocks" ) );
|
|
QList< QgsMapCanvas * > views;
|
|
if ( !nodes.isEmpty() )
|
|
{
|
|
QDomNode viewNode = nodes.at( 0 );
|
|
nodes = viewNode.childNodes();
|
|
for ( int i = 0; i < nodes.size(); ++i )
|
|
{
|
|
QDomElement elementNode = nodes.at( i ).toElement();
|
|
QString mapName = elementNode.attribute( QStringLiteral( "name" ) );
|
|
int x = elementNode.attribute( QStringLiteral( "x" ), QStringLiteral( "0" ) ).toInt();
|
|
int y = elementNode.attribute( QStringLiteral( "y" ), QStringLiteral( "0" ) ).toInt();
|
|
int w = elementNode.attribute( QStringLiteral( "width" ), QStringLiteral( "400" ) ).toInt();
|
|
int h = elementNode.attribute( QStringLiteral( "height" ), QStringLiteral( "400" ) ).toInt();
|
|
bool floating = elementNode.attribute( QStringLiteral( "floating" ), QStringLiteral( "0" ) ).toInt();
|
|
bool synced = elementNode.attribute( QStringLiteral( "synced" ), QStringLiteral( "0" ) ).toInt();
|
|
bool showCursor = elementNode.attribute( QStringLiteral( "showCursor" ), QStringLiteral( "0" ) ).toInt();
|
|
bool showExtent = elementNode.attribute( QStringLiteral( "showExtent" ), QStringLiteral( "0" ) ).toInt();
|
|
bool scaleSynced = elementNode.attribute( QStringLiteral( "scaleSynced" ), QStringLiteral( "0" ) ).toInt();
|
|
double scaleFactor = elementNode.attribute( QStringLiteral( "scaleFactor" ), QStringLiteral( "1" ) ).toDouble();
|
|
bool showLabels = elementNode.attribute( QStringLiteral( "showLabels" ), QStringLiteral( "1" ) ).toInt();
|
|
Qt::DockWidgetArea area = static_cast< Qt::DockWidgetArea >( elementNode.attribute( QStringLiteral( "area" ), QString::number( Qt::RightDockWidgetArea ) ).toInt() );
|
|
|
|
QgsMapCanvasDockWidget *mapCanvasDock = createNewMapCanvasDock( mapName, floating, QRect( x, y, w, h ), area );
|
|
QgsMapCanvas *mapCanvas = mapCanvasDock->mapCanvas();
|
|
mapCanvasDock->setViewCenterSynchronized( synced );
|
|
mapCanvasDock->setCursorMarkerVisible( showCursor );
|
|
mapCanvasDock->setScaleFactor( scaleFactor );
|
|
mapCanvasDock->setViewScaleSynchronized( scaleSynced );
|
|
mapCanvasDock->setMainCanvasExtentVisible( showExtent );
|
|
mapCanvasDock->setLabelsVisible( showLabels );
|
|
mapCanvas->readProject( doc );
|
|
views << mapCanvas;
|
|
}
|
|
}
|
|
// unfreeze all new views at once. We don't do this as they are created since additional
|
|
// views which may exist in project could rearrange the docks and cause the canvases to resize
|
|
// resulting in multiple redraws
|
|
Q_FOREACH ( QgsMapCanvas *c, views )
|
|
{
|
|
c->freeze( false );
|
|
}
|
|
}
|
|
|
|
void QgisApp::showLayerProperties( QgsMapLayer *ml )
|
|
{
|
|
/*
|
|
TODO: Consider reusing the property dialogs again.
|
|
Sometimes around mid 2005, the property dialogs were saved for later reuse;
|
|
this resulted in a time savings when reopening the dialog. The code below
|
|
cannot be used as is, however, simply by saving the dialog pointer here.
|
|
Either the map layer needs to be passed as an argument to sync or else
|
|
a separate copy of the dialog pointer needs to be stored with each layer.
|
|
*/
|
|
|
|
if ( !ml )
|
|
return;
|
|
|
|
if ( !QgsProject::instance()->layerIsEmbedded( ml->id() ).isEmpty() )
|
|
{
|
|
return; //don't show properties of embedded layers
|
|
}
|
|
|
|
if ( ml->type() == QgsMapLayer::RasterLayer )
|
|
{
|
|
#if 0 // See note above about reusing this
|
|
QgsRasterLayerProperties *rlp = nullptr;
|
|
if ( rlp )
|
|
{
|
|
rlp->sync();
|
|
}
|
|
else
|
|
{
|
|
rlp = new QgsRasterLayerProperties( ml, mMapCanvas, this );
|
|
// handled by rendererChanged() connect( rlp, SIGNAL( refreshLegend( QString, bool ) ), mLayerTreeView, SLOT( refreshLayerSymbology( QString ) ) );
|
|
}
|
|
#else
|
|
QgsRasterLayerProperties *rlp = new QgsRasterLayerProperties( ml, mMapCanvas, this );
|
|
#endif
|
|
|
|
rlp->exec();
|
|
delete rlp; // delete since dialog cannot be reused without updating code
|
|
}
|
|
else if ( ml->type() == QgsMapLayer::VectorLayer ) // VECTOR
|
|
{
|
|
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( ml );
|
|
|
|
#if 0 // See note above about reusing this
|
|
QgsVectorLayerProperties *vlp = nullptr;
|
|
if ( vlp )
|
|
{
|
|
vlp->syncToLayer();
|
|
}
|
|
else
|
|
{
|
|
vlp = new QgsVectorLayerProperties( vlayer, this );
|
|
// handled by rendererChanged() connect( vlp, SIGNAL( refreshLegend( QString ) ), mLayerTreeView, SLOT( refreshLayerSymbology( QString ) ) );
|
|
}
|
|
#else
|
|
QgsVectorLayerProperties *vlp = new QgsVectorLayerProperties( vlayer, this );
|
|
#endif
|
|
Q_FOREACH ( QgsMapLayerConfigWidgetFactory *factory, mMapLayerPanelFactories )
|
|
{
|
|
vlp->addPropertiesPageFactory( factory );
|
|
}
|
|
|
|
mMapStyleWidget->blockUpdates( true );
|
|
if ( vlp->exec() )
|
|
{
|
|
activateDeactivateLayerRelatedActions( ml );
|
|
mMapStyleWidget->updateCurrentWidgetLayer();
|
|
}
|
|
mMapStyleWidget->blockUpdates( false );
|
|
|
|
delete vlp; // delete since dialog cannot be reused without updating code
|
|
}
|
|
else if ( ml->type() == QgsMapLayer::PluginLayer )
|
|
{
|
|
QgsPluginLayer *pl = qobject_cast<QgsPluginLayer *>( ml );
|
|
if ( !pl )
|
|
return;
|
|
|
|
QgsPluginLayerType *plt = QgsApplication::pluginLayerRegistry()->pluginLayerType( pl->pluginLayerType() );
|
|
if ( !plt )
|
|
return;
|
|
|
|
if ( !plt->showLayerProperties( pl ) )
|
|
{
|
|
messageBar()->pushMessage( tr( "Warning" ),
|
|
tr( "This layer doesn't have a properties dialog." ),
|
|
QgsMessageBar::INFO, messageTimeout() );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgisApp::namSetup()
|
|
{
|
|
QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance();
|
|
|
|
connect( nam, &QNetworkAccessManager::authenticationRequired,
|
|
this, &QgisApp::namAuthenticationRequired );
|
|
|
|
connect( nam, &QNetworkAccessManager::proxyAuthenticationRequired,
|
|
this, &QgisApp::namProxyAuthenticationRequired );
|
|
|
|
connect( nam, &QgsNetworkAccessManager::requestTimedOut,
|
|
this, &QgisApp::namRequestTimedOut );
|
|
|
|
#ifndef QT_NO_SSL
|
|
connect( nam, &QNetworkAccessManager::sslErrors,
|
|
this, &QgisApp::namSslErrors );
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::namAuthenticationRequired( QNetworkReply *inReply, QAuthenticator *auth )
|
|
{
|
|
QPointer<QNetworkReply> reply( inReply );
|
|
Q_ASSERT( qApp->thread() == QThread::currentThread() );
|
|
|
|
QString username = auth->user();
|
|
QString password = auth->password();
|
|
|
|
if ( username.isEmpty() && password.isEmpty() && reply->request().hasRawHeader( "Authorization" ) )
|
|
{
|
|
QByteArray header( reply->request().rawHeader( "Authorization" ) );
|
|
if ( header.startsWith( "Basic " ) )
|
|
{
|
|
QByteArray auth( QByteArray::fromBase64( header.mid( 6 ) ) );
|
|
int pos = auth.indexOf( ':' );
|
|
if ( pos >= 0 )
|
|
{
|
|
username = auth.left( pos );
|
|
password = auth.mid( pos + 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( ;; )
|
|
{
|
|
bool ok;
|
|
|
|
{
|
|
QMutexLocker lock( QgsCredentials::instance()->mutex() );
|
|
ok = QgsCredentials::instance()->get(
|
|
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
|
|
username, password,
|
|
tr( "Authentication required" ) );
|
|
}
|
|
if ( !ok )
|
|
return;
|
|
|
|
if ( reply.isNull() || reply->isFinished() )
|
|
return;
|
|
|
|
if ( auth->user() != username || ( password != auth->password() && !password.isNull() ) )
|
|
break;
|
|
|
|
// credentials didn't change - stored ones probably wrong? clear password and retry
|
|
{
|
|
QMutexLocker lock( QgsCredentials::instance()->mutex() );
|
|
QgsCredentials::instance()->put(
|
|
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
|
|
username, QString() );
|
|
}
|
|
}
|
|
|
|
// save credentials
|
|
{
|
|
QMutexLocker lock( QgsCredentials::instance()->mutex() );
|
|
QgsCredentials::instance()->put(
|
|
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
|
|
username, password
|
|
);
|
|
}
|
|
|
|
auth->setUser( username );
|
|
auth->setPassword( password );
|
|
}
|
|
|
|
void QgisApp::namProxyAuthenticationRequired( const QNetworkProxy &proxy, QAuthenticator *auth )
|
|
{
|
|
QgsSettings settings;
|
|
if ( !settings.value( QStringLiteral( "proxy/proxyEnabled" ), false ).toBool() ||
|
|
settings.value( QStringLiteral( "proxy/proxyType" ), "" ).toString() == QLatin1String( "DefaultProxy" ) )
|
|
{
|
|
auth->setUser( QLatin1String( "" ) );
|
|
return;
|
|
}
|
|
|
|
QString username = auth->user();
|
|
QString password = auth->password();
|
|
|
|
for ( ;; )
|
|
{
|
|
bool ok;
|
|
|
|
{
|
|
QMutexLocker lock( QgsCredentials::instance()->mutex() );
|
|
ok = QgsCredentials::instance()->get(
|
|
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
|
|
username, password,
|
|
tr( "Proxy authentication required" ) );
|
|
}
|
|
if ( !ok )
|
|
return;
|
|
|
|
if ( auth->user() != username || ( password != auth->password() && !password.isNull() ) )
|
|
break;
|
|
|
|
// credentials didn't change - stored ones probably wrong? clear password and retry
|
|
{
|
|
QMutexLocker lock( QgsCredentials::instance()->mutex() );
|
|
QgsCredentials::instance()->put(
|
|
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
|
|
username, QString() );
|
|
}
|
|
}
|
|
|
|
{
|
|
QMutexLocker lock( QgsCredentials::instance()->mutex() );
|
|
QgsCredentials::instance()->put(
|
|
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
|
|
username, password
|
|
);
|
|
}
|
|
|
|
auth->setUser( username );
|
|
auth->setPassword( password );
|
|
}
|
|
|
|
#ifndef QT_NO_SSL
|
|
void QgisApp::namSslErrors( QNetworkReply *reply, const QList<QSslError> &errors )
|
|
{
|
|
// stop the timeout timer, or app crashes if the user (or slot) takes longer than
|
|
// singleshot timeout and tries to update the closed QNetworkReply
|
|
QTimer *timer = reply->findChild<QTimer *>( QStringLiteral( "timeoutTimer" ) );
|
|
if ( timer )
|
|
{
|
|
QgsDebugMsg( "Stopping network reply timeout" );
|
|
timer->stop();
|
|
}
|
|
|
|
QgsDebugMsg( QString( "SSL errors occurred accessing URL:\n%1" ).arg( reply->request().url().toString() ) );
|
|
|
|
QString hostport( QStringLiteral( "%1:%2" )
|
|
.arg( reply->url().host() )
|
|
.arg( reply->url().port() != -1 ? reply->url().port() : 443 )
|
|
.trimmed() );
|
|
QString digest( QgsAuthCertUtils::shaHexForCert( reply->sslConfiguration().peerCertificate() ) );
|
|
QString dgsthostport( QStringLiteral( "%1:%2" ).arg( digest, hostport ) );
|
|
|
|
const QHash<QString, QSet<QSslError::SslError> > &errscache( QgsAuthManager::instance()->getIgnoredSslErrorCache() );
|
|
|
|
if ( errscache.contains( dgsthostport ) )
|
|
{
|
|
QgsDebugMsg( QString( "Ignored SSL errors cahced item found, ignoring errors if they match for %1" ).arg( hostport ) );
|
|
const QSet<QSslError::SslError> &errenums( errscache.value( dgsthostport ) );
|
|
bool ignore = !errenums.isEmpty();
|
|
int errmatched = 0;
|
|
if ( ignore )
|
|
{
|
|
Q_FOREACH ( const QSslError &error, errors )
|
|
{
|
|
if ( error.error() == QSslError::NoError )
|
|
continue;
|
|
|
|
bool errmatch = errenums.contains( error.error() );
|
|
ignore = ignore && errmatch;
|
|
errmatched += errmatch ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
if ( ignore && errenums.size() == errmatched )
|
|
{
|
|
QgsDebugMsg( QString( "Errors matched cached item's, ignoring all for %1" ).arg( hostport ) );
|
|
reply->ignoreSslErrors();
|
|
return;
|
|
}
|
|
|
|
QgsDebugMsg( QString( "Errors %1 for cached item for %2" )
|
|
.arg( errenums.isEmpty() ? "not found" : "did not match",
|
|
hostport ) );
|
|
}
|
|
|
|
|
|
QgsAuthSslErrorsDialog *dlg = new QgsAuthSslErrorsDialog( reply, errors, this, digest, hostport );
|
|
dlg->setWindowModality( Qt::ApplicationModal );
|
|
dlg->resize( 580, 512 );
|
|
if ( dlg->exec() )
|
|
{
|
|
if ( reply )
|
|
{
|
|
QgsDebugMsg( QString( "All SSL errors ignored for %1" ).arg( hostport ) );
|
|
reply->ignoreSslErrors();
|
|
}
|
|
}
|
|
dlg->deleteLater();
|
|
|
|
// restart network request timeout timer
|
|
if ( reply )
|
|
{
|
|
QgsSettings s;
|
|
QTimer *timer = reply->findChild<QTimer *>( QStringLiteral( "timeoutTimer" ) );
|
|
if ( timer )
|
|
{
|
|
QgsDebugMsg( "Restarting network reply timeout" );
|
|
timer->setSingleShot( true );
|
|
timer->start( s.value( QStringLiteral( "/qgis/networkAndProxy/networkTimeout" ), "60000" ).toInt() );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void QgisApp::namRequestTimedOut( QNetworkReply *reply )
|
|
{
|
|
Q_UNUSED( reply );
|
|
QLabel *msgLabel = new QLabel( tr( "A network request timed out, any data received is likely incomplete." ) +
|
|
tr( " Please check the <a href=\"#messageLog\">message log</a> for further info." ), messageBar() );
|
|
msgLabel->setWordWrap( true );
|
|
connect( msgLabel, &QLabel::linkActivated, mLogDock, &QWidget::show );
|
|
messageBar()->pushItem( new QgsMessageBarItem( msgLabel, QgsMessageBar::WARNING, messageTimeout() ) );
|
|
}
|
|
|
|
void QgisApp::namUpdate()
|
|
{
|
|
QgsNetworkAccessManager::instance()->setupDefaultProxyAndCache();
|
|
}
|
|
|
|
void QgisApp::masterPasswordSetup()
|
|
{
|
|
connect( QgsAuthManager::instance(), &QgsAuthManager::messageOut,
|
|
this, &QgisApp::authMessageOut );
|
|
connect( QgsAuthManager::instance(), &QgsAuthManager::passwordHelperMessageOut,
|
|
this, &QgisApp::authMessageOut );
|
|
connect( QgsAuthManager::instance(), &QgsAuthManager::authDatabaseEraseRequested,
|
|
this, &QgisApp::eraseAuthenticationDatabase );
|
|
}
|
|
|
|
void QgisApp::eraseAuthenticationDatabase()
|
|
{
|
|
// First check if now is a good time to interact with the user, e.g. project is done loading.
|
|
// If not, ask QgsAuthManager to re-emit authDatabaseEraseRequested from the schedule timer.
|
|
// No way to know if user interaction will interfere with plugins loading layers.
|
|
|
|
if ( !QgsProject::instance()->fileName().isNull() ) // a non-blank project is loaded
|
|
{
|
|
// Apparently, as of QGIS 2.9, the only way to query that the project is in a
|
|
// layer-loading state is via a custom property of the project's layer tree.
|
|
QgsLayerTreeGroup *layertree( QgsProject::instance()->layerTreeRoot() );
|
|
if ( layertree && layertree->customProperty( QStringLiteral( "loading" ) ).toBool() )
|
|
{
|
|
QgsDebugMsg( "Project loading, skipping auth db erase" );
|
|
QgsAuthManager::instance()->setScheduledAuthDatabaseEraseRequestEmitted( false );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// TODO: Check is Browser panel is also still loading?
|
|
// It has auto-connections in parallel (if tree item is expanded), though
|
|
// such connections with possible master password requests *should* be ignored
|
|
// when there is an authentication db erase scheduled.
|
|
|
|
// This function should tell QgsAuthManager to stop any erase db schedule timer,
|
|
// *after* interacting with the user
|
|
QgsAuthGuiUtils::eraseAuthenticationDatabase( messageBar(), messageTimeout(), this );
|
|
}
|
|
|
|
void QgisApp::authMessageOut( const QString &message, const QString &authtag, QgsAuthManager::MessageLevel level )
|
|
{
|
|
// Use system notifications if the main window is not the active one,
|
|
// push message to the message bar if the main window is active
|
|
if ( qApp->activeWindow() != this )
|
|
{
|
|
showSystemNotification( tr( "QGIS Authentication" ), message );
|
|
}
|
|
else
|
|
{
|
|
int levelint = static_cast< int >( level );
|
|
messageBar()->pushMessage( authtag, message, static_cast< QgsMessageBar::MessageLevel >( levelint ), 7 );
|
|
}
|
|
}
|
|
|
|
void QgisApp::completeInitialization()
|
|
{
|
|
emit initializationCompleted();
|
|
}
|
|
|
|
void QgisApp::toolButtonActionTriggered( QAction *action )
|
|
{
|
|
QToolButton *bt = qobject_cast<QToolButton *>( sender() );
|
|
if ( !bt )
|
|
return;
|
|
|
|
QgsSettings settings;
|
|
if ( action == mActionSelectFeatures )
|
|
settings.setValue( QStringLiteral( "UI/selectTool" ), 1 );
|
|
else if ( action == mActionSelectRadius )
|
|
settings.setValue( QStringLiteral( "UI/selectTool" ), 2 );
|
|
else if ( action == mActionSelectPolygon )
|
|
settings.setValue( QStringLiteral( "UI/selectTool" ), 3 );
|
|
else if ( action == mActionSelectFreehand )
|
|
settings.setValue( QStringLiteral( "UI/selectTool" ), 4 );
|
|
else if ( action == mActionMeasure )
|
|
settings.setValue( QStringLiteral( "UI/measureTool" ), 0 );
|
|
else if ( action == mActionMeasureArea )
|
|
settings.setValue( QStringLiteral( "UI/measureTool" ), 1 );
|
|
else if ( action == mActionMeasureAngle )
|
|
settings.setValue( QStringLiteral( "UI/measureTool" ), 2 );
|
|
else if ( action == mActionTextAnnotation )
|
|
settings.setValue( QStringLiteral( "UI/annotationTool" ), 0 );
|
|
else if ( action == mActionFormAnnotation )
|
|
settings.setValue( QStringLiteral( "UI/annotationTool" ), 1 );
|
|
else if ( action == mActionHtmlAnnotation )
|
|
settings.setValue( QStringLiteral( "UI/annotationTool" ), 2 );
|
|
else if ( action == mActionSvgAnnotation )
|
|
settings.setValue( QStringLiteral( "UI/annotationTool" ), 3 );
|
|
else if ( action == mActionAnnotation )
|
|
settings.setValue( QStringLiteral( "UI/annotationTool" ), 4 );
|
|
else if ( action == mActionNewSpatiaLiteLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 0 );
|
|
else if ( action == mActionNewVectorLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 1 );
|
|
else if ( action == mActionNewMemoryLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 2 );
|
|
else if ( action == mActionNewGeoPackageLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 3 );
|
|
else if ( action == mActionRotatePointSymbols )
|
|
settings.setValue( QStringLiteral( "UI/defaultPointSymbolAction" ), 0 );
|
|
else if ( action == mActionOffsetPointSymbol )
|
|
settings.setValue( QStringLiteral( "UI/defaultPointSymbolAction" ), 1 );
|
|
else if ( mActionAddPgLayer && action == mActionAddPgLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultAddDbLayerAction" ), 0 );
|
|
else if ( mActionAddMssqlLayer && action == mActionAddMssqlLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultAddDbLayerAction" ), 1 );
|
|
else if ( mActionAddDb2Layer && action == mActionAddDb2Layer )
|
|
settings.setValue( QStringLiteral( "UI/defaultAddDbLayerAction" ), 2 );
|
|
else if ( mActionAddOracleLayer && action == mActionAddOracleLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultAddDbLayerAction" ), 3 );
|
|
else if ( action == mActionAddWfsLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultFeatureService" ), 0 );
|
|
else if ( action == mActionAddAfsLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultFeatureService" ), 1 );
|
|
else if ( action == mActionAddWmsLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultMapService" ), 0 );
|
|
else if ( action == mActionAddAmsLayer )
|
|
settings.setValue( QStringLiteral( "UI/defaultMapService" ), 1 );
|
|
else if ( action == mActionMoveFeature )
|
|
settings.setValue( QStringLiteral( "UI/defaultMoveTool" ), 0 );
|
|
else if ( action == mActionMoveFeatureCopy )
|
|
settings.setValue( QStringLiteral( "UI/defaultMoveTool" ), 1 );
|
|
|
|
bt->setDefaultAction( action );
|
|
}
|
|
|
|
QMenu *QgisApp::createPopupMenu()
|
|
{
|
|
QMenu *menu = QMainWindow::createPopupMenu();
|
|
QList< QAction * > al = menu->actions();
|
|
QList< QAction * > panels, toolbars;
|
|
|
|
if ( !al.isEmpty() )
|
|
{
|
|
bool found = false;
|
|
for ( int i = 0; i < al.size(); ++i )
|
|
{
|
|
if ( al[ i ]->isSeparator() )
|
|
{
|
|
found = true;
|
|
continue;
|
|
}
|
|
|
|
if ( !found )
|
|
{
|
|
panels.append( al[ i ] );
|
|
}
|
|
else
|
|
{
|
|
toolbars.append( al[ i ] );
|
|
}
|
|
}
|
|
|
|
std::sort( panels.begin(), panels.end(), cmpByText_ );
|
|
QWidgetAction *panelstitle = new QWidgetAction( menu );
|
|
QLabel *plabel = new QLabel( QStringLiteral( "<b>%1</b>" ).arg( tr( "Panels" ) ) );
|
|
plabel->setMargin( 3 );
|
|
plabel->setAlignment( Qt::AlignHCenter );
|
|
panelstitle->setDefaultWidget( plabel );
|
|
menu->addAction( panelstitle );
|
|
Q_FOREACH ( QAction *a, panels )
|
|
{
|
|
menu->addAction( a );
|
|
}
|
|
menu->addSeparator();
|
|
QWidgetAction *toolbarstitle = new QWidgetAction( menu );
|
|
QLabel *tlabel = new QLabel( QStringLiteral( "<b>%1</b>" ).arg( tr( "Toolbars" ) ) );
|
|
tlabel->setMargin( 3 );
|
|
tlabel->setAlignment( Qt::AlignHCenter );
|
|
toolbarstitle->setDefaultWidget( tlabel );
|
|
menu->addAction( toolbarstitle );
|
|
std::sort( toolbars.begin(), toolbars.end(), cmpByText_ );
|
|
Q_FOREACH ( QAction *a, toolbars )
|
|
{
|
|
menu->addAction( a );
|
|
}
|
|
}
|
|
|
|
return menu;
|
|
}
|
|
|
|
|
|
void QgisApp::showSystemNotification( const QString &title, const QString &message )
|
|
{
|
|
// Menubar icon is hidden by default. Show to enable notification bubbles
|
|
mTray->show();
|
|
mTray->showMessage( title, message );
|
|
// Re-hide menubar icon
|
|
mTray->hide();
|
|
}
|
|
|
|
void QgisApp::osmDownloadDialog()
|
|
{
|
|
QgsOSMDownloadDialog dlg;
|
|
dlg.exec();
|
|
}
|
|
|
|
void QgisApp::osmImportDialog()
|
|
{
|
|
QgsOSMImportDialog dlg;
|
|
dlg.exec();
|
|
}
|
|
|
|
void QgisApp::osmExportDialog()
|
|
{
|
|
QgsOSMExportDialog dlg;
|
|
dlg.exec();
|
|
}
|
|
|
|
void QgisApp::showStatisticsDockWidget()
|
|
{
|
|
mStatisticalSummaryDockWidget->show();
|
|
mStatisticalSummaryDockWidget->raise();
|
|
}
|
|
|
|
void QgisApp::onLayerError( const QString &msg )
|
|
{
|
|
QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( sender() );
|
|
|
|
Q_ASSERT( layer );
|
|
|
|
mInfoBar->pushCritical( tr( "Layer %1" ).arg( layer->name() ), msg );
|
|
}
|
|
|
|
bool QgisApp::gestureEvent( QGestureEvent *event )
|
|
{
|
|
#ifdef Q_OS_ANDROID
|
|
if ( QGesture *tapAndHold = event->gesture( Qt::TapAndHoldGesture ) )
|
|
{
|
|
tapAndHoldTriggered( static_cast<QTapAndHoldGesture *>( tapAndHold ) );
|
|
}
|
|
return true;
|
|
#else
|
|
Q_UNUSED( event );
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void QgisApp::tapAndHoldTriggered( QTapAndHoldGesture *gesture )
|
|
{
|
|
if ( gesture->state() == Qt::GestureFinished )
|
|
{
|
|
QPoint pos = gesture->position().toPoint();
|
|
QWidget *receiver = QApplication::widgetAt( pos );
|
|
qDebug() << "tapAndHoldTriggered: LONG CLICK gesture happened at " << pos;
|
|
qDebug() << "widget under point of click: " << receiver;
|
|
|
|
QApplication::postEvent( receiver, new QMouseEvent( QEvent::MouseButtonPress, receiver->mapFromGlobal( pos ), Qt::RightButton, Qt::RightButton, Qt::NoModifier ) );
|
|
QApplication::postEvent( receiver, new QMouseEvent( QEvent::MouseButtonRelease, receiver->mapFromGlobal( pos ), Qt::RightButton, Qt::RightButton, Qt::NoModifier ) );
|
|
}
|
|
}
|
|
|
|
void QgisApp::transactionGroupCommitError( const QString &error )
|
|
{
|
|
displayMessage( tr( "Transaction" ), error, QgsMessageBar::CRITICAL );
|
|
}
|