Skip to content

Commit

Permalink
[FEATURE] Allow overwriting the project home path
Browse files Browse the repository at this point in the history
This allows the project home path (which is used by the browser
to create the 'Project Home' item) to be set by users for a
project, instead of always matching the location where the project
is saved.

This allows users to set the project home to a folder which contains
data and other content, and is especially useful for organisations
where qgis projects are not stored in the root folder of a organisational
'project'.

Project home paths can also be set to relative paths, in which
case they will be relative to the project saved location.

The path can be set through the Project Properties dialog, or
by right-clicking on the Project Home browser item and
selecting 'set project home'

Sponsored by SMEC/SJ
  • Loading branch information
nyalldawson committed Mar 9, 2018
1 parent d09a34c commit 4e5c08e
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 70 deletions.
51 changes: 48 additions & 3 deletions python/core/qgsproject.sip.in
Expand Up @@ -442,9 +442,34 @@ Sets the default area measurement units for the project.

QString homePath() const;
%Docstring
Return project's home path
Returns the project's home path. This will either be a manually set home path
(see presetHomePath()) or the path containing the project file itself.

:return: home path of project (or null QString if not set) *
This method always returns the absolute path to the project's home. See
presetHomePath() to retrieve any manual project home path override (e.g.
relative home paths).

.. seealso:: :py:func:`setPresetHomePath`

.. seealso:: :py:func:`presetHomePath`

.. seealso:: :py:func:`homePathChanged`
%End

QString presetHomePath() const;
%Docstring
Returns any manual project home path setting, or an empty string if not set.

This path may be a relative path. See homePath() to retrieve a path which is always
an absolute path.

.. seealso:: :py:func:`homePath`

.. seealso:: :py:func:`setPresetHomePath`

.. seealso:: :py:func:`homePathChanged`

.. versionadded:: 3.2
%End

QgsRelationManager *relationManager() const;
Expand Down Expand Up @@ -937,7 +962,13 @@ Emitted when the file name of the project changes

void homePathChanged();
%Docstring
Emitted when the home path of the project changes
Emitted when the home path of the project changes.

.. seealso:: :py:func:`setPresetHomePath`

.. seealso:: :py:func:`homePath`

.. seealso:: :py:func:`presetHomePath`
%End

void snappingConfigChanged( const QgsSnappingConfig &config );
Expand Down Expand Up @@ -1170,6 +1201,20 @@ be asked to save changes to the project before closing the current project.
promoted to public slot in 2.16
%End

void setPresetHomePath( const QString &path );
%Docstring
Sets the project's home ``path``. If an empty path is specified than the
home path will be automatically determined from the project's file path.

.. versionadded:: 3.2

.. seealso:: :py:func:`presetHomePath`

.. seealso:: :py:func:`homePath`

.. seealso:: :py:func:`homePathChanged`
%End

};


Expand Down
36 changes: 36 additions & 0 deletions src/app/qgsprojectproperties.cpp
Expand Up @@ -204,6 +204,41 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa
mAutoTransaction->setChecked( QgsProject::instance()->autoTransaction() );
title( QgsProject::instance()->title() );
mProjectFileLineEdit->setText( QDir::toNativeSeparators( QgsProject::instance()->fileName() ) );
mProjectHomeLineEdit->setShowClearButton( true );
mProjectHomeLineEdit->setText( QDir::toNativeSeparators( QgsProject::instance()->presetHomePath() ) );
connect( mButtonSetProjectHome, &QToolButton::clicked, this, [ = ]
{
auto getAbsoluteHome = [this]()->QString
{
QString currentHome = QDir::fromNativeSeparators( mProjectHomeLineEdit->text() );
if ( !currentHome.isEmpty() )
{
QFileInfo homeInfo( currentHome );
if ( !homeInfo.isRelative() )
return currentHome;
}

QFileInfo pfi( QgsProject::instance()->fileName() );
if ( !pfi.exists() )
return QDir::homePath();

if ( !currentHome.isEmpty() )
{
// path is relative to project file
return QDir::cleanPath( pfi.path() + '/' + currentHome );
}
else
{
return pfi.canonicalPath();
}
};

QString newDir = QFileDialog::getExistingDirectory( this, tr( "Select Project Home Path" ), getAbsoluteHome() );
if ( ! newDir.isNull() )
{
mProjectHomeLineEdit->setText( QDir::toNativeSeparators( newDir ) );
}
} );

connect( mButtonOpenProjectFolder, &QToolButton::clicked, this, [ = ]
{
Expand Down Expand Up @@ -833,6 +868,7 @@ void QgsProjectProperties::apply()

// Set the project title
QgsProject::instance()->setTitle( title() );
QgsProject::instance()->setPresetHomePath( QDir::fromNativeSeparators( mProjectHomeLineEdit->text() ) );
QgsProject::instance()->setAutoTransaction( mAutoTransaction->isChecked() );
QgsProject::instance()->setEvaluateDefaultValues( mEvaluateDefaultValues->isChecked() );
QgsProject::instance()->setTrustLayerMetadata( mTrustProjectCheckBox->isChecked() );
Expand Down
3 changes: 2 additions & 1 deletion src/core/qgsbrowsermodel.cpp
Expand Up @@ -177,7 +177,8 @@ void QgsBrowserModel::initialize()
if ( ! mInitialized )
{
connect( QgsProject::instance(), &QgsProject::readProject, this, &QgsBrowserModel::updateProjectHome );
connect( QgsProject::instance(), &QgsProject::writeProject, this, &QgsBrowserModel::updateProjectHome );
connect( QgsProject::instance(), &QgsProject::projectSaved, this, &QgsBrowserModel::updateProjectHome );
connect( QgsProject::instance(), &QgsProject::homePathChanged, this, &QgsBrowserModel::updateProjectHome );
addRootItems();
mInitialized = true;
}
Expand Down
24 changes: 24 additions & 0 deletions src/core/qgsdataitem.cpp
Expand Up @@ -28,6 +28,7 @@
#include <QVector>
#include <QStyle>
#include <QDesktopServices>
#include <QFileDialog>

#include "qgis.h"
#include "qgsdataitem.h"
Expand All @@ -40,6 +41,7 @@
#include "qgsconfig.h"
#include "qgssettings.h"
#include "qgsanimatedicon.h"
#include "qgsproject.h"

// use GDAL VSI mechanism
#define CPL_SUPRESS_CPLUSPLUS //#spellok
Expand Down Expand Up @@ -1614,6 +1616,28 @@ QVariant QgsProjectHomeItem::sortKey() const
return QStringLiteral( " 1" );
}

QList<QAction *> QgsProjectHomeItem::actions( QWidget *parent )
{
QList<QAction *> lst = QgsDirectoryItem::actions( parent );
QAction *separator = new QAction( parent );
separator->setSeparator( true );
lst.append( separator );

QAction *setHome = new QAction( tr( "Set Project Home…" ), parent );
connect( setHome, &QAction::triggered, this, [ = ]
{
QString oldHome = QgsProject::instance()->homePath();
QString newPath = QFileDialog::getExistingDirectory( parent, tr( "Select Project Home Directory" ), oldHome );
if ( !newPath.isEmpty() )
{
QgsProject::instance()->setPresetHomePath( newPath );
}
} );
lst << setHome;

return lst;
}

QgsFavoriteItem::QgsFavoriteItem( QgsFavoritesItem *parent, const QString &name, const QString &dirPath, const QString &path )
: QgsDirectoryItem( parent, name, dirPath, path )
, mFavorites( parent )
Expand Down
3 changes: 3 additions & 0 deletions src/core/qgsdataitem.h
Expand Up @@ -745,6 +745,9 @@ class CORE_EXPORT QgsProjectHomeItem : public QgsDirectoryItem
QIcon icon() override;
QVariant sortKey() const override;

QList<QAction *> actions( QWidget *parent ) override;


};

/**
Expand Down
53 changes: 51 additions & 2 deletions src/core/qgsproject.cpp
Expand Up @@ -417,6 +417,17 @@ void QgsProject::setDirty( bool b )
emit isDirtyChanged( mDirty );
}

void QgsProject::setPresetHomePath( const QString &path )
{
if ( path == mHomePath )
return;

mHomePath = path;
emit homePathChanged();

setDirty( true );
}

void QgsProject::setFileName( const QString &name )
{
if ( name == mFile.fileName() )
Expand Down Expand Up @@ -488,6 +499,7 @@ void QgsProject::clear()
mFile.setFileName( QString() );
mProperties.clearKeys();
mTitle.clear();
mHomePath.clear();
mAutoTransaction = false;
mEvaluateDefaultValues = false;
mDirty = false;
Expand Down Expand Up @@ -895,6 +907,19 @@ bool QgsProject::readProjectFile( const QString &filename )
// now get project title
_getTitle( *doc, mTitle );

QDomNodeList homePathNl = doc->elementsByTagName( QStringLiteral( "homePath" ) );
if ( homePathNl.count() > 0 )
{
QDomElement homePathElement = homePathNl.at( 0 ).toElement();
QString homePath = homePathElement.attribute( QStringLiteral( "path" ) );
if ( !homePath.isEmpty() )
setPresetHomePath( homePath );
}
else
{
emit homePathChanged();
}

QgsReadWriteContext context;
context.setPathResolver( pathResolver() );

Expand Down Expand Up @@ -1370,6 +1395,10 @@ bool QgsProject::writeProjectFile( const QString &filename )

doc->appendChild( qgisNode );

QDomElement homePathNode = doc->createElement( QStringLiteral( "homePath" ) );
homePathNode.setAttribute( QStringLiteral( "path" ), mHomePath );
qgisNode.appendChild( homePathNode );

// title
QDomElement titleNode = doc->createElement( QStringLiteral( "title" ) );
qgisNode.appendChild( titleNode );
Expand Down Expand Up @@ -2077,11 +2106,31 @@ void QgsProject::setAreaUnits( QgsUnitTypes::AreaUnit unit )

QString QgsProject::homePath() const
{
if ( !mHomePath.isEmpty() )
{
QFileInfo homeInfo( mHomePath );
if ( !homeInfo.isRelative() )
return mHomePath;
}

QFileInfo pfi( fileName() );
if ( !pfi.exists() )
return QString();
return mHomePath;

return pfi.canonicalPath();
if ( !mHomePath.isEmpty() )
{
// path is relative to project file
return QDir::cleanPath( pfi.path() + '/' + mHomePath );
}
else
{
return pfi.canonicalPath();
}
}

QString QgsProject::presetHomePath() const
{
return mHomePath;
}

QgsRelationManager *QgsProject::relationManager() const
Expand Down
52 changes: 48 additions & 4 deletions src/core/qgsproject.h
Expand Up @@ -85,7 +85,7 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
Q_OBJECT
Q_PROPERTY( QStringList nonIdentifiableLayers READ nonIdentifiableLayers WRITE setNonIdentifiableLayers NOTIFY nonIdentifiableLayersChanged )
Q_PROPERTY( QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged )
Q_PROPERTY( QString homePath READ homePath NOTIFY homePathChanged )
Q_PROPERTY( QString homePath READ homePath WRITE setPresetHomePath NOTIFY homePathChanged )
Q_PROPERTY( QgsCoordinateReferenceSystem crs READ crs WRITE setCrs NOTIFY crsChanged )
Q_PROPERTY( QString ellipsoid READ ellipsoid WRITE setEllipsoid NOTIFY ellipsoidChanged )
Q_PROPERTY( QgsMapThemeCollection *mapThemeCollection READ mapThemeCollection NOTIFY mapThemeCollectionChanged )
Expand Down Expand Up @@ -423,10 +423,33 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
void setAreaUnits( QgsUnitTypes::AreaUnit unit );

/**
* Return project's home path
\returns home path of project (or null QString if not set) */
* Returns the project's home path. This will either be a manually set home path
* (see presetHomePath()) or the path containing the project file itself.
*
* This method always returns the absolute path to the project's home. See
* presetHomePath() to retrieve any manual project home path override (e.g.
* relative home paths).
*
* \see setPresetHomePath()
* \see presetHomePath()
* \see homePathChanged()
*/
QString homePath() const;

/**
* Returns any manual project home path setting, or an empty string if not set.
*
* This path may be a relative path. See homePath() to retrieve a path which is always
* an absolute path.
*
* \see homePath()
* \see setPresetHomePath()
* \see homePathChanged()
*
* \since QGIS 3.2
*/
QString presetHomePath() const;

QgsRelationManager *relationManager() const;

/**
Expand Down Expand Up @@ -905,7 +928,12 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
//! Emitted when the file name of the project changes
void fileNameChanged();

//! Emitted when the home path of the project changes
/**
* Emitted when the home path of the project changes.
* \see setPresetHomePath()
* \see homePath()
* \see presetHomePath()
*/
void homePathChanged();

//! emitted whenever the configuration for snapping has changed
Expand Down Expand Up @@ -1117,6 +1145,16 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
*/
void setDirty( bool b = true );

/**
* Sets the project's home \a path. If an empty path is specified than the
* home path will be automatically determined from the project's file path.
* \since QGIS 3.2
* \see presetHomePath()
* \see homePath()
* \see homePathChanged()
*/
void setPresetHomePath( const QString &path );

private slots:
void onMapLayersAdded( const QList<QgsMapLayer *> &layers );
void onMapLayersRemoved( const QList<QgsMapLayer *> &layers );
Expand Down Expand Up @@ -1212,6 +1250,12 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
std::unique_ptr<QgsAuxiliaryStorage> mAuxiliaryStorage;

QFile mFile; // current physical project file

/**
* Manual override for project home path - if empty, home path is automatically
* created based on file name.
*/
QString mHomePath;
mutable QgsProjectPropertyKey mProperties; // property hierarchy, TODO: this shouldn't be mutable
QString mTitle; // project title
bool mAutoTransaction = false; // transaction grouped editing
Expand Down

0 comments on commit 4e5c08e

Please sign in to comment.