mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
Add QgsProjectDirtyBlocker and QgsProject.blockDirtying to prevent
project dirtying for the lifetime of an object Python code can then call: project = QgsProject.instance() with QgsProject.blockDirtying(project): # do something Use QgsProjectDirtyBlocker to prevent projects being marked as dirty while creating a new project or while loading an existing project -- avoids the titlebar temporarily showing the project state as unsaved while it is being loaded.
This commit is contained in:
parent
d6eeabf69b
commit
60afeadf44
@ -234,6 +234,38 @@ class ReadWriteContextEnterCategory():
|
||||
QgsReadWriteContext.enterCategory = ReadWriteContextEnterCategory
|
||||
|
||||
|
||||
# Python class to extend QgsProjectDirtyBlocker C++ class
|
||||
|
||||
|
||||
class ProjectDirtyBlocker():
|
||||
"""
|
||||
Context manager used to block project setDirty calls.
|
||||
|
||||
Example:
|
||||
project = QgsProject.instance()
|
||||
with QgsProject.blockDirtying(project):
|
||||
# do something
|
||||
|
||||
.. versionadded:: 3.2
|
||||
"""
|
||||
|
||||
def __init__(self, project):
|
||||
self.project = project
|
||||
self.blocker = None
|
||||
|
||||
def __enter__(self):
|
||||
self.blocker = QgsProjectDirtyBlocker(self.project)
|
||||
return self.project
|
||||
|
||||
def __exit__(self, ex_type, ex_value, traceback):
|
||||
del self.blocker
|
||||
return True
|
||||
|
||||
|
||||
# Inject the context manager into QgsProject class as a member
|
||||
QgsProject.blockDirtying = ProjectDirtyBlocker
|
||||
|
||||
|
||||
class QgsTaskWrapper(QgsTask):
|
||||
|
||||
def __init__(self, description, flags, function, on_finished, *args, **kwargs):
|
||||
|
@ -1189,6 +1189,7 @@ The snapping configuration for this project.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
|
||||
void setDirty( bool b = true );
|
||||
%Docstring
|
||||
Flag the project as dirty (modified). If this flag is set, the user will
|
||||
@ -1217,6 +1218,45 @@ home path will be automatically determined from the project's file path.
|
||||
|
||||
};
|
||||
|
||||
class QgsProjectDirtyBlocker
|
||||
{
|
||||
%Docstring
|
||||
Temporarily blocks QgsProject "dirtying" for the lifetime of the object.
|
||||
|
||||
QgsProjectDirtyBlocker supports "stacked" blocking, so two QgsProjectDirtyBlockers created
|
||||
for the same project will both need to be destroyed before the project can be dirtied again.
|
||||
|
||||
Note that QgsProjectDirtyBlocker only blocks calls which set the project as dirty - calls
|
||||
which set the project as clean are not blocked.
|
||||
|
||||
Python scripts should not use QgsProjectDirtyBlocker directly. Instead, use :py:func:`QgsProject.blockDirtying()`
|
||||
.. code-block:: python
|
||||
|
||||
project = QgsProject.instance()
|
||||
with QgsProject.blockDirtying(project):
|
||||
# do something
|
||||
|
||||
.. seealso:: :py:func:`QgsProject.setDirty`
|
||||
|
||||
.. versionadded:: 3.2
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsproject.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsProjectDirtyBlocker( QgsProject *project );
|
||||
%Docstring
|
||||
Constructor for QgsProjectDirtyBlocker.
|
||||
|
||||
This will block dirtying the specified ``project`` for the lifetime of this object.
|
||||
%End
|
||||
|
||||
~QgsProjectDirtyBlocker();
|
||||
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
|
@ -5066,6 +5066,7 @@ bool QgisApp::fileNew( bool promptToSaveFlag, bool forceBlank )
|
||||
|
||||
QgsSettings settings;
|
||||
|
||||
MAYBE_UNUSED QgsProjectDirtyBlocker dirtyBlocker( QgsProject::instance() );
|
||||
closeProject();
|
||||
|
||||
QgsProject *prj = QgsProject::instance();
|
||||
@ -5472,7 +5473,7 @@ void QgisApp::fileOpen()
|
||||
// open the selected project
|
||||
addProject( fullPath );
|
||||
}
|
||||
} // QgisApp::fileOpen
|
||||
}
|
||||
|
||||
void QgisApp::enableProjectMacros()
|
||||
{
|
||||
@ -5488,6 +5489,8 @@ void QgisApp::enableProjectMacros()
|
||||
*/
|
||||
bool QgisApp::addProject( const QString &projectFile )
|
||||
{
|
||||
MAYBE_UNUSED QgsProjectDirtyBlocker dirtyBlocker( QgsProject::instance() );
|
||||
|
||||
// close the previous opened project if any
|
||||
closeProject();
|
||||
|
||||
|
@ -411,9 +411,15 @@ bool QgsProject::isDirty() const
|
||||
return mDirty;
|
||||
}
|
||||
|
||||
void QgsProject::setDirty( bool b )
|
||||
void QgsProject::setDirty( const bool dirty )
|
||||
{
|
||||
mDirty = b;
|
||||
if ( dirty && mDirtyBlockCount > 0 )
|
||||
return;
|
||||
|
||||
if ( mDirty == dirty )
|
||||
return;
|
||||
|
||||
mDirty = dirty;
|
||||
emit isDirtyChanged( mDirty );
|
||||
}
|
||||
|
||||
|
@ -1136,6 +1136,8 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
|
||||
*/
|
||||
void setSnappingConfig( const QgsSnappingConfig &snappingConfig );
|
||||
|
||||
// TODO QGIS 4.0 - rename b to dirty
|
||||
|
||||
/**
|
||||
* Flag the project as dirty (modified). If this flag is set, the user will
|
||||
* be asked to save changes to the project before closing the current project.
|
||||
@ -1262,9 +1264,57 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
|
||||
bool mEvaluateDefaultValues = false; // evaluate default values immediately
|
||||
QgsCoordinateReferenceSystem mCrs;
|
||||
bool mDirty = false; // project has been modified since it has been read or saved
|
||||
int mDirtyBlockCount = 0;
|
||||
bool mTrustLayerMetadata = false;
|
||||
|
||||
QgsCoordinateTransformContext mTransformContext;
|
||||
|
||||
friend class QgsProjectDirtyBlocker;
|
||||
};
|
||||
|
||||
/**
|
||||
* Temporarily blocks QgsProject "dirtying" for the lifetime of the object.
|
||||
*
|
||||
* QgsProjectDirtyBlocker supports "stacked" blocking, so two QgsProjectDirtyBlockers created
|
||||
* for the same project will both need to be destroyed before the project can be dirtied again.
|
||||
*
|
||||
* Note that QgsProjectDirtyBlocker only blocks calls which set the project as dirty - calls
|
||||
* which set the project as clean are not blocked.
|
||||
*
|
||||
* Python scripts should not use QgsProjectDirtyBlocker directly. Instead, use QgsProject.blockDirtying()
|
||||
* \code{.py}
|
||||
* project = QgsProject.instance()
|
||||
* with QgsProject.blockDirtying(project):
|
||||
* # do something
|
||||
* \endcode
|
||||
*
|
||||
* \see QgsProject::setDirty()
|
||||
*
|
||||
* \ingroup core
|
||||
* \since QGIS 3.2
|
||||
*/
|
||||
class CORE_EXPORT QgsProjectDirtyBlocker
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsProjectDirtyBlocker.
|
||||
*
|
||||
* This will block dirtying the specified \a project for the lifetime of this object.
|
||||
*/
|
||||
QgsProjectDirtyBlocker( QgsProject *project )
|
||||
: mProject( project )
|
||||
{
|
||||
mProject->mDirtyBlockCount++;
|
||||
}
|
||||
|
||||
~QgsProjectDirtyBlocker()
|
||||
{
|
||||
mProject->mDirtyBlockCount--;
|
||||
}
|
||||
|
||||
private:
|
||||
QgsProject *mProject = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,7 @@ import os
|
||||
import qgis # NOQA
|
||||
|
||||
from qgis.core import (QgsProject,
|
||||
QgsProjectDirtyBlocker,
|
||||
QgsApplication,
|
||||
QgsUnitTypes,
|
||||
QgsCoordinateReferenceSystem,
|
||||
@ -930,6 +931,78 @@ class TestQgsProject(unittest.TestCase):
|
||||
scope = QgsExpressionContextUtils.projectScope(p)
|
||||
self.assertEqual(scope.variable('project_home'), '../home')
|
||||
|
||||
def testDirtyBlocker(self):
|
||||
# first test manual QgsProjectDirtyBlocker construction
|
||||
p = QgsProject()
|
||||
|
||||
dirty_spy = QSignalSpy(p.isDirtyChanged)
|
||||
# ^ will do *whatever* it takes to discover the enemy's secret plans!
|
||||
|
||||
# simple checks
|
||||
p.setDirty(True)
|
||||
self.assertTrue(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 1)
|
||||
self.assertEqual(dirty_spy[-1], [True])
|
||||
p.setDirty(True) # already dirty
|
||||
self.assertTrue(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 1)
|
||||
p.setDirty(False)
|
||||
self.assertFalse(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 2)
|
||||
self.assertEqual(dirty_spy[-1], [False])
|
||||
p.setDirty(True)
|
||||
self.assertTrue(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 3)
|
||||
self.assertEqual(dirty_spy[-1], [True])
|
||||
|
||||
# with a blocker
|
||||
blocker = QgsProjectDirtyBlocker(p)
|
||||
# blockers will allow cleaning projects
|
||||
p.setDirty(False)
|
||||
self.assertFalse(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 4)
|
||||
self.assertEqual(dirty_spy[-1], [False])
|
||||
# but not dirtying!
|
||||
p.setDirty(True)
|
||||
self.assertFalse(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 4)
|
||||
self.assertEqual(dirty_spy[-1], [False])
|
||||
# nested block
|
||||
blocker2 = QgsProjectDirtyBlocker(p)
|
||||
p.setDirty(True)
|
||||
self.assertFalse(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 4)
|
||||
self.assertEqual(dirty_spy[-1], [False])
|
||||
del blocker2
|
||||
p.setDirty(True)
|
||||
self.assertFalse(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 4)
|
||||
self.assertEqual(dirty_spy[-1], [False])
|
||||
del blocker
|
||||
p.setDirty(True)
|
||||
self.assertTrue(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 5)
|
||||
self.assertEqual(dirty_spy[-1], [True])
|
||||
|
||||
# using python context manager
|
||||
with QgsProject.blockDirtying(p):
|
||||
# cleaning allowed
|
||||
p.setDirty(False)
|
||||
self.assertFalse(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 6)
|
||||
self.assertEqual(dirty_spy[-1], [False])
|
||||
# but not dirtying!
|
||||
p.setDirty(True)
|
||||
self.assertFalse(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 6)
|
||||
self.assertEqual(dirty_spy[-1], [False])
|
||||
|
||||
# unblocked
|
||||
p.setDirty(True)
|
||||
self.assertTrue(p.isDirty())
|
||||
self.assertEqual(len(dirty_spy), 7)
|
||||
self.assertEqual(dirty_spy[-1], [True])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user