Implement framework for history providers

As per https://github.com/qgis/QGIS-Enhancement-Proposals/issues/130
This commit is contained in:
Nyall Dawson 2021-12-17 09:51:23 +10:00
parent 0d332d34ec
commit 6783347896
17 changed files with 957 additions and 0 deletions

View File

@ -104,6 +104,7 @@ if(WITH_APIDOC)
${CMAKE_SOURCE_DIR}/src/gui/editorwidgets
${CMAKE_SOURCE_DIR}/src/gui/editorwidgets/core
${CMAKE_SOURCE_DIR}/src/gui/effects
${CMAKE_SOURCE_DIR}/src/gui/history
${CMAKE_SOURCE_DIR}/src/gui/labeling
${CMAKE_SOURCE_DIR}/src/gui/layertree
${CMAKE_SOURCE_DIR}/src/gui/layout

View File

@ -1187,3 +1187,10 @@ QgsCurve.CounterClockwise.__doc__ = "Counter-clockwise direction"
Qgis.AngularDirection.__doc__ = 'Angular directions.\n\n.. versionadded:: 3.24\n\n' + '* ``Clockwise``: ' + Qgis.AngularDirection.Clockwise.__doc__ + '\n' + '* ``CounterClockwise``: ' + Qgis.AngularDirection.CounterClockwise.__doc__
# --
Qgis.AngularDirection.baseClass = Qgis
# monkey patching scoped based enum
Qgis.HistoryProviderBackend.LocalProfile.__doc__ = "Local profile"
Qgis.HistoryProviderBackend.__doc__ = 'History provider backends.\n\n.. versionadded:: 3.24\n\n' + '* ``LocalProfile``: ' + Qgis.HistoryProviderBackend.LocalProfile.__doc__
# --
Qgis.HistoryProviderBackend.baseClass = Qgis
Qgis.HistoryProviderBackends.baseClass = Qgis
HistoryProviderBackends = Qgis # dirty hack since SIP seems to introduce the flags in module

View File

@ -763,6 +763,14 @@ The development version
CounterClockwise,
};
enum class HistoryProviderBackend
{
LocalProfile,
// Project = 1 << 1, //!< QGIS Project (not yet implemented)
};
typedef QFlags<Qgis::HistoryProviderBackend> HistoryProviderBackends;
static const double DEFAULT_SEARCH_RADIUS_MM;
static const float DEFAULT_MAPTOPIXEL_THRESHOLD;
@ -866,6 +874,8 @@ QFlags<Qgis::MarkerLinePlacement> operator|(Qgis::MarkerLinePlacement f1, QFlags
QFlags<Qgis::TextRendererFlag> operator|(Qgis::TextRendererFlag f1, QFlags<Qgis::TextRendererFlag> f2);
QFlags<Qgis::HistoryProviderBackend> operator|(Qgis::HistoryProviderBackend f1, QFlags<Qgis::HistoryProviderBackend> f2);

View File

@ -0,0 +1,45 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/history/qgshistoryprovider.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsAbstractHistoryProvider
{
%Docstring(signature="appended")
Abstract base class for objects which track user history (i.e. operations performed through the GUI).
:py:class:`QgsAbstractHistoryProvider` subclasses are accessible through the :py:class:`QgsHistoryProviderRegistry` class.
.. versionadded:: 3.24
%End
%TypeHeaderCode
#include "qgshistoryprovider.h"
%End
public:
virtual ~QgsAbstractHistoryProvider();
virtual QString id() const = 0;
%Docstring
Returns the provider's unique id, which is used to associate existing history entries with the provider.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/history/qgshistoryprovider.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,163 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/history/qgshistoryproviderregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsHistoryEntry
{
%Docstring(signature="appended")
Encapsulates a history entry.
.. versionadded:: 3.24
%End
%TypeHeaderCode
#include "qgshistoryproviderregistry.h"
%End
public:
QgsHistoryEntry( const QString &providerId, const QDateTime &timestamp, const QVariantMap &entry );
%Docstring
Constructor for QgsHistoryEntry ``entry``, with the specified ``providerId`` and ``timestamp``.
%End
QgsHistoryEntry( const QVariantMap &entry );
%Docstring
Constructor for QgsHistoryEntry ``entry``.
The entry timestamp will be automatically set to the current date/time.
%End
QDateTime timestamp;
QString providerId;
QVariantMap entry;
SIP_PYOBJECT __repr__();
%MethodCode
const QString str = QStringLiteral( "<QgsHistoryEntry: %1 %2>" ).arg( sipCpp->providerId, sipCpp->timestamp.toString( Qt::ISODate ) );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
};
class QgsHistoryProviderRegistry : QObject
{
%Docstring(signature="appended")
The :py:class:`QgsHistoryProviderRegistry` is a registry for objects which track user history (i.e. operations performed through the GUI).
:py:class:`QgsHistoryProviderRegistry` is not usually directly created, but rather accessed through
:py:func:`QgsGui.historyProviderRegistry()`.
.. versionadded:: 3.24
%End
%TypeHeaderCode
#include "qgshistoryproviderregistry.h"
%End
public:
QgsHistoryProviderRegistry( QObject *parent = 0, bool useMemoryDatabase = false );
%Docstring
Creates a new empty history provider registry.
QgsHistoryProviderRegistry is not usually directly created, but rather accessed through
:py:func:`QgsGui.historyProviderRegistry()`.
%End
~QgsHistoryProviderRegistry();
bool addProvider( QgsAbstractHistoryProvider *provider /Transfer/ );
%Docstring
Adds a ``provider`` to the registry. Ownership of the provider is
transferred to the registry.
Returns ``True`` if the provider was successfully added.
%End
QgsAbstractHistoryProvider *providerById( const QString &id );
%Docstring
Returns the provider with matching ``id``, or ``None`` if no matching
provider is registered.
%End
bool removeProvider( const QString &id );
%Docstring
Removes the provider with matching ``id``.
The provider will be deleted.
Returns ``True`` if the provider was successfully removed.
%End
QStringList providerIds() const;
%Docstring
Returns a list of the registered provider IDs.
%End
class HistoryEntryOptions
{
%Docstring(signature="appended")
Contains options for storing history entries.
.. versionadded:: 3.24
%End
%TypeHeaderCode
#include "qgshistoryproviderregistry.h"
%End
public:
HistoryEntryOptions();
%Docstring
Constructor for HistoryEntryOptions.
%End
Qgis::HistoryProviderBackends storageBackends;
};
bool addEntry( const QString &providerId, const QVariantMap &entry, QgsHistoryProviderRegistry::HistoryEntryOptions options = QgsHistoryProviderRegistry::HistoryEntryOptions() );
%Docstring
Adds an ``entry`` to the history logs.
The entry will be tagged with the current date/time as the timestamp.
The ``providerId`` specifies the history provider responsible for this entry.
Entry options are specified via the ``options`` argument.
%End
bool addEntry( const QgsHistoryEntry &entry, QgsHistoryProviderRegistry::HistoryEntryOptions options = QgsHistoryProviderRegistry::HistoryEntryOptions() );
%Docstring
Adds an ``entry`` to the history logs.
%End
QList< QgsHistoryEntry > queryEntries( const QDateTime &start = QDateTime(), const QDateTime &end = QDateTime(),
const QString &providerId = QString(), Qgis::HistoryProviderBackends backends = Qgis::HistoryProviderBackend::LocalProfile ) const;
%Docstring
Queries history entries which occurred between the specified ``start`` and ``end`` times.
The optional ``providerId`` and ``backends`` arguments can be used to filter entries.
%End
static QString userHistoryDbPath();
%Docstring
Returns the path to user's local history database.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/history/qgshistoryproviderregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -153,6 +153,14 @@ Returns the global relation widget registry, used for managing all known relatio
.. versionadded:: 3.18
%End
static QgsHistoryProviderRegistry *historyProviderRegistry() /KeepReference/;
%Docstring
Returns the global history provider registry, used for tracking history providers.
.. versionadded:: 3.24
%End
static void enableAutoGeometryRestore( QWidget *widget, const QString &key = QString() );
%Docstring
Register the widget to allow its position to be automatically saved and restored when open and closed.

View File

@ -317,6 +317,8 @@
%Include auto_generated/effects/qgseffectstackpropertieswidget.sip
%Include auto_generated/effects/qgspainteffectpropertieswidget.sip
%Include auto_generated/effects/qgspainteffectwidget.sip
%Include auto_generated/history/qgshistoryprovider.sip
%Include auto_generated/history/qgshistoryproviderregistry.sip
%Include auto_generated/labeling/qgslabellineanchorwidget.sip
%Include auto_generated/labeling/qgslabelobstaclesettingswidget.sip
%Include auto_generated/labeling/qgslabelsettingswidgetbase.sip

View File

@ -1248,6 +1248,20 @@ class CORE_EXPORT Qgis
};
Q_ENUM( AngularDirection )
/**
* History provider backends.
*
* \since QGIS 3.24
*/
enum class HistoryProviderBackend : int
{
LocalProfile = 1 << 0, //!< Local profile
// Project = 1 << 1, //!< QGIS Project (not yet implemented)
};
Q_ENUM( HistoryProviderBackend )
Q_DECLARE_FLAGS( HistoryProviderBackends, HistoryProviderBackend )
Q_FLAG( HistoryProviderBackends )
/**
* Identify search radius in mm
* \since QGIS 2.3
@ -1380,6 +1394,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::RenderContextFlags )
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::VectorLayerTypeFlags )
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::MarkerLinePlacements )
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::TextRendererFlags )
Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::HistoryProviderBackends )
// hack to workaround warnings when casting void pointers

View File

@ -212,6 +212,9 @@ set(QGIS_GUI_SRCS
editorwidgets/qgsvaluerelationsearchwidgetwrapper.cpp
editorwidgets/qgsvaluerelationwidgetfactory.cpp
history/qgshistoryprovider.cpp
history/qgshistoryproviderregistry.cpp
labeling/qgslabelengineconfigdialog.cpp
labeling/qgslabelinggui.cpp
labeling/qgslabelingwidget.cpp
@ -1033,6 +1036,9 @@ set(QGIS_GUI_HDRS
effects/qgspainteffectpropertieswidget.h
effects/qgspainteffectwidget.h
history/qgshistoryprovider.h
history/qgshistoryproviderregistry.h
labeling/qgslabelengineconfigdialog.h
labeling/qgslabelinggui.h
labeling/qgslabelingwidget.h
@ -1449,6 +1455,7 @@ target_include_directories(qgis_gui PUBLIC
${CMAKE_SOURCE_DIR}/src/gui/editorwidgets
${CMAKE_SOURCE_DIR}/src/gui/editorwidgets/core
${CMAKE_SOURCE_DIR}/src/gui/effects
${CMAKE_SOURCE_DIR}/src/gui/history
${CMAKE_SOURCE_DIR}/src/gui/labeling
${CMAKE_SOURCE_DIR}/src/gui/layertree
${CMAKE_SOURCE_DIR}/src/gui/layout

View File

@ -0,0 +1,19 @@
/***************************************************************************
qgshistoryprovider.cpp
-------------------------
begin : April 2019
copyright : (C) 2019 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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 "qgshistoryprovider.h"
QgsAbstractHistoryProvider::~QgsAbstractHistoryProvider() = default;

View File

@ -0,0 +1,58 @@
/***************************************************************************
qgshistoryprovider.h
--------------------------
begin : April 2019
copyright : (C) 2019 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSHISTORYPROVIDER_H
#define QGSHISTORYPROVIDER_H
#include "qgis_gui.h"
#include "qgis_sip.h"
#include <QString>
#include <QVariantMap>
class QgsHistoryEntryNode;
/**
* Abstract base class for objects which track user history (i.e. operations performed through the GUI).
*
* QgsAbstractHistoryProvider subclasses are accessible through the QgsHistoryProviderRegistry class.
*
* \ingroup gui
* \since QGIS 3.24
*/
class GUI_EXPORT QgsAbstractHistoryProvider
{
public:
virtual ~QgsAbstractHistoryProvider();
/**
* Returns the provider's unique id, which is used to associate existing history entries with the provider.
*/
virtual QString id() const = 0;
#if 0
/**
* Creates a new history node for the given \a entry.
*/
virtual QgsHistoryEntryNode *createNodeForEntry( const QVariantMap &entry ) = 0 SIP_FACTORY;
#endif
};
#endif //QGSHISTORYPROVIDER_H

View File

@ -0,0 +1,247 @@
/***************************************************************************
qgshistoryproviderregistry.cpp
-------------------------
begin : April 2019
copyright : (C) 2019 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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 "qgshistoryproviderregistry.h"
#include "history/qgshistoryprovider.h"
#include "qgsapplication.h"
#include "qgsruntimeprofiler.h"
#include "qgslogger.h"
#include "qgsxmlutils.h"
#include <QFile>
#include <sqlite3.h>
//
// QgsHistoryEntry
//
QgsHistoryEntry::QgsHistoryEntry( const QString &providerId, const QDateTime &timestamp, const QVariantMap &entry )
: timestamp( timestamp )
, providerId( providerId )
, entry( entry )
{
}
QgsHistoryEntry::QgsHistoryEntry( const QVariantMap &entry )
: timestamp( QDateTime::currentDateTime() )
, entry( entry )
{
}
//
// QgsHistoryProviderRegistry
//
QgsHistoryProviderRegistry::QgsHistoryProviderRegistry( QObject *parent, bool useMemoryDatabase )
: QObject( parent )
{
QgsScopedRuntimeProfile profile( tr( "Load history database" ) );
const QString historyFilename = userHistoryDbPath();
// create history db if it doesn't exist
QString error;
if ( useMemoryDatabase )
{
createDatabase( QStringLiteral( ":memory:" ), error );
}
else
{
if ( !QFile::exists( historyFilename ) )
{
createDatabase( historyFilename, error );
}
else
{
openDatabase( historyFilename, error );
}
}
}
QgsHistoryProviderRegistry::~QgsHistoryProviderRegistry()
{
qDeleteAll( mProviders );
}
bool QgsHistoryProviderRegistry::addProvider( QgsAbstractHistoryProvider *provider )
{
if ( mProviders.contains( provider->id() ) )
return false;
mProviders.insert( provider->id(), provider );
return true;
}
QgsAbstractHistoryProvider *QgsHistoryProviderRegistry::providerById( const QString &id )
{
return mProviders.value( id );
}
bool QgsHistoryProviderRegistry::removeProvider( const QString &id )
{
if ( !mProviders.contains( id ) )
return false;
delete mProviders.take( id );
return true;
}
QStringList QgsHistoryProviderRegistry::providerIds() const
{
return mProviders.keys();
}
bool QgsHistoryProviderRegistry::addEntry( const QString &providerId, const QVariantMap &entry, QgsHistoryProviderRegistry::HistoryEntryOptions options )
{
return addEntry( QgsHistoryEntry( providerId, QDateTime::currentDateTime(), entry ), options );
}
bool QgsHistoryProviderRegistry::addEntry( const QgsHistoryEntry &entry, HistoryEntryOptions options )
{
if ( options.storageBackends & Qgis::HistoryProviderBackend::LocalProfile )
{
QDomDocument xmlDoc;
xmlDoc.appendChild( QgsXmlUtils::writeVariant( entry.entry, xmlDoc ) );
const QString entryXml = xmlDoc.toString();
const QString dateTime = entry.timestamp.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) );
QString query = qgs_sqlite3_mprintf( "INSERT INTO history VALUES (NULL, '%q', '%q', '%q');",
entry.providerId.toUtf8().constData(), entryXml.toUtf8().constData(), dateTime.toUtf8().constData() );
if ( !runEmptyQuery( query ) )
{
QgsDebugMsg( QStringLiteral( "Couldn't story history entry in database!" ) );
return false;
}
}
return true;
}
QList<QgsHistoryEntry> QgsHistoryProviderRegistry::queryEntries( const QDateTime &start, const QDateTime &end, const QString &providerId, Qgis::HistoryProviderBackends backends ) const
{
QList<QgsHistoryEntry> entries;
if ( backends & Qgis::HistoryProviderBackend::LocalProfile )
{
if ( !mLocalDB )
{
QgsDebugMsg( QStringLiteral( "Cannot open database to query history entries" ) );
return {};
}
QString sql = QStringLiteral( "SELECT provider_id, xml, timestamp FROM history" );
QStringList whereClauses;
if ( !providerId.isEmpty() )
{
whereClauses.append( QStringLiteral( "provider_id='%1'" ).arg( providerId ) );
}
if ( start.isValid() )
{
whereClauses.append( QStringLiteral( "timestamp>='%1'" ).arg( start.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
}
if ( end.isValid() )
{
whereClauses.append( QStringLiteral( "timestamp<='%1'" ).arg( end.toString( QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ) ) );
}
if ( !whereClauses.empty() )
sql += QStringLiteral( " WHERE (" ) + whereClauses.join( QStringLiteral( ") AND (" ) ) + ')';
int nErr;
sqlite3_statement_unique_ptr statement = mLocalDB.prepare( sql, nErr );
while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
{
QDomDocument doc;
if ( !doc.setContent( statement.columnAsText( 1 ) ) )
{
QgsDebugMsg( QStringLiteral( "Cannot read history entry" ) );
continue;
}
entries.append( QgsHistoryEntry(
statement.columnAsText( 0 ),
QDateTime::fromString( statement.columnAsText( 2 ), QStringLiteral( "yyyy-MM-dd HH:mm:ss" ) ),
QgsXmlUtils::readVariant( doc.documentElement() ).toMap()
) );
}
}
return entries;
}
QString QgsHistoryProviderRegistry::userHistoryDbPath()
{
return QgsApplication::qgisSettingsDirPath() + QStringLiteral( "user-history.db" );
}
bool QgsHistoryProviderRegistry::createDatabase( const QString &filename, QString &error )
{
error.clear();
if ( !openDatabase( filename, error ) )
{
QgsDebugMsg( error );
return false;
}
createTables();
return true;
}
bool QgsHistoryProviderRegistry::openDatabase( const QString &filename, QString &error )
{
int rc = mLocalDB.open( filename );
if ( rc )
{
error = tr( "Couldn't open the history database: %1" ).arg( mLocalDB.errorMessage() );
return false;
}
return true;
}
void QgsHistoryProviderRegistry::createTables()
{
QString query = qgs_sqlite3_mprintf( "CREATE TABLE history("\
"id INTEGER PRIMARY KEY,"\
"provider_id TEXT,"\
"xml TEXT,"\
"timestamp DATETIME);" \
"CREATE INDEX provider_index ON history(provider_id);"\
"CREATE INDEX timestamp_index ON history(timestamp);"
);
runEmptyQuery( query );
}
bool QgsHistoryProviderRegistry::runEmptyQuery( const QString &query )
{
if ( !mLocalDB )
return false;
char *zErr = nullptr;
int nErr = sqlite3_exec( mLocalDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
if ( nErr != SQLITE_OK )
{
QgsDebugMsg( zErr );
sqlite3_free( zErr );
}
return nErr == SQLITE_OK;
}

View File

@ -0,0 +1,209 @@
/***************************************************************************
qgshistoryproviderregistry.h
--------------------------
begin : April 2019
copyright : (C) 2019 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef QGSHISTORYPROVIDERREGISTRY_H
#define QGSHISTORYPROVIDERREGISTRY_H
#include "qgis_gui.h"
#include "qgis_sip.h"
#include "qgis.h"
#include <QObject>
#include <QMap>
#include <QString>
#include <QDateTime>
#include <QVariant>
#include <QVector>
#include "qgssqliteutils.h"
class QgsAbstractHistoryProvider;
/**
* Encapsulates a history entry.
*
* \ingroup gui
* \since QGIS 3.24
*/
class GUI_EXPORT QgsHistoryEntry
{
public:
/**
* Constructor for QgsHistoryEntry \a entry, with the specified \a providerId and \a timestamp.
*/
QgsHistoryEntry( const QString &providerId, const QDateTime &timestamp, const QVariantMap &entry );
/**
* Constructor for QgsHistoryEntry \a entry.
*
* The entry timestamp will be automatically set to the current date/time.
*/
QgsHistoryEntry( const QVariantMap &entry );
//! Entry timestamp
QDateTime timestamp;
//! Associated history provider ID
QString providerId;
/**
* Entry details.
*
* Entries details are stored as a free-form map. Interpretation of this map is the responsiblity of the
* associated history provider.
*/
QVariantMap entry;
#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode
const QString str = QStringLiteral( "<QgsHistoryEntry: %1 %2>" ).arg( sipCpp->providerId, sipCpp->timestamp.toString( Qt::ISODate ) );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
#endif
};
/**
* The QgsHistoryProviderRegistry is a registry for objects which track user history (i.e. operations performed through the GUI).
*
* QgsHistoryProviderRegistry is not usually directly created, but rather accessed through
* QgsGui::historyProviderRegistry().
*
* \ingroup gui
* \since QGIS 3.24
*/
class GUI_EXPORT QgsHistoryProviderRegistry : public QObject
{
Q_OBJECT
public:
/**
* Creates a new empty history provider registry.
*
* QgsHistoryProviderRegistry is not usually directly created, but rather accessed through
* QgsGui::historyProviderRegistry().
*/
QgsHistoryProviderRegistry( QObject *parent = nullptr, bool useMemoryDatabase = false );
~QgsHistoryProviderRegistry() override;
/**
* Adds a \a provider to the registry. Ownership of the provider is
* transferred to the registry.
*
* Returns TRUE if the provider was successfully added.
*/
bool addProvider( QgsAbstractHistoryProvider *provider SIP_TRANSFER );
/**
* Returns the provider with matching \a id, or NULLPTR if no matching
* provider is registered.
*/
QgsAbstractHistoryProvider *providerById( const QString &id );
/**
* Removes the provider with matching \a id.
*
* The provider will be deleted.
*
* Returns TRUE if the provider was successfully removed.
*/
bool removeProvider( const QString &id );
/**
* Returns a list of the registered provider IDs.
*/
QStringList providerIds() const;
/**
* Contains options for storing history entries.
*
* \ingroup gui
* \since QGIS 3.24
*/
class HistoryEntryOptions
{
public:
/**
* Constructor for HistoryEntryOptions.
*/
HistoryEntryOptions() {}
//! Target storage backends
Qgis::HistoryProviderBackends storageBackends = Qgis::HistoryProviderBackend::LocalProfile;
};
/**
* Adds an \a entry to the history logs.
*
* The entry will be tagged with the current date/time as the timestamp.
*
* The \a providerId specifies the history provider responsible for this entry.
* Entry options are specified via the \a options argument.
*/
bool addEntry( const QString &providerId, const QVariantMap &entry, QgsHistoryProviderRegistry::HistoryEntryOptions options = QgsHistoryProviderRegistry::HistoryEntryOptions() );
/**
* Adds an \a entry to the history logs.
*/
bool addEntry( const QgsHistoryEntry &entry, QgsHistoryProviderRegistry::HistoryEntryOptions options = QgsHistoryProviderRegistry::HistoryEntryOptions() );
/**
* Queries history entries which occurred between the specified \a start and \a end times.
*
* The optional \a providerId and \a backends arguments can be used to filter entries.
*/
QList< QgsHistoryEntry > queryEntries( const QDateTime &start = QDateTime(), const QDateTime &end = QDateTime(),
const QString &providerId = QString(), Qgis::HistoryProviderBackends backends = Qgis::HistoryProviderBackend::LocalProfile ) const;
/**
* Returns the path to user's local history database.
*/
static QString userHistoryDbPath();
private:
/**
* Creates an on-disk history database.
*/
bool createDatabase( const QString &filename, QString &error );
//! Convenience function to open the DB
bool openDatabase( const QString &filename, QString &error );
/**
* Creates tables structure for history database.
*/
void createTables();
/**
* Convenience function that would run queries which don't generate return values
*
* \param query query to run
* \returns success TRUE on success
*/
bool runEmptyQuery( const QString &query );
QMap< QString, QgsAbstractHistoryProvider * > mProviders;
sqlite3_database_unique_ptr mLocalDB;
};
#endif //QGSHISTORYPROVIDERREGISTRY_H

View File

@ -63,6 +63,7 @@
#include "qgsprovidersourcewidgetproviderregistry.h"
#include "qgsrelationwidgetregistry.h"
#include "qgssettingsregistrygui.h"
#include "qgshistoryproviderregistry.h"
QgsGui *QgsGui::instance()
{
@ -165,6 +166,11 @@ QgsProviderGuiRegistry *QgsGui::providerGuiRegistry()
return instance()->mProviderGuiRegistry;
}
QgsHistoryProviderRegistry *QgsGui::historyProviderRegistry()
{
return instance()->mHistoryProviderRegistry;
}
void QgsGui::enableAutoGeometryRestore( QWidget *widget, const QString &key )
{
if ( widget->objectName().isEmpty() )
@ -207,6 +213,7 @@ QgsGui::~QgsGui()
delete mEditorWidgetRegistry;
delete mMapLayerActionRegistry;
delete mSourceSelectProviderRegistry;
delete mHistoryProviderRegistry;
delete mShortcutsManager;
delete mNative;
delete mNumericFormatGuiRegistry;
@ -268,6 +275,7 @@ QgsGui::QgsGui()
mCodeEditorColorSchemeRegistry = new QgsCodeEditorColorSchemeRegistry();
// provider gui registry initialize QgsProviderRegistry too
mHistoryProviderRegistry = new QgsHistoryProviderRegistry();
mProviderGuiRegistry = new QgsProviderGuiRegistry( QgsApplication::pluginPath() );
mProjectStorageGuiRegistry = new QgsProjectStorageGuiRegistry();
mDataItemGuiProviderRegistry = new QgsDataItemGuiProviderRegistry();

View File

@ -45,6 +45,7 @@ class QgsMessageBar;
class QgsSubsetStringEditorProviderRegistry;
class QgsProviderSourceWidgetProviderRegistry;
class QgsRelationWidgetRegistry;
class QgsHistoryProviderRegistry;
/**
* \ingroup gui
@ -190,6 +191,13 @@ class GUI_EXPORT QgsGui : public QObject
*/
static QgsRelationWidgetRegistry *relationWidgetRegistry() SIP_KEEPREFERENCE;
/**
* Returns the global history provider registry, used for tracking history providers.
* \since QGIS 3.24
*/
static QgsHistoryProviderRegistry *historyProviderRegistry() SIP_KEEPREFERENCE;
/**
* Register the widget to allow its position to be automatically saved and restored when open and closed.
* Use this to avoid needing to call saveGeometry() and restoreGeometry() on your widget.
@ -296,6 +304,7 @@ class GUI_EXPORT QgsGui : public QObject
QgsSubsetStringEditorProviderRegistry *mSubsetStringEditorProviderRegistry = nullptr;
QgsProviderSourceWidgetProviderRegistry *mProviderSourceWidgetProviderRegistry = nullptr;
QgsRelationWidgetRegistry *mRelationEditorRegistry = nullptr;
QgsHistoryProviderRegistry *mHistoryProviderRegistry = nullptr;
std::unique_ptr< QgsWindowManagerInterface > mWindowManager;
#ifdef SIP_RUN

View File

@ -122,6 +122,7 @@ ADD_PYTHON_TEST(PyQgsGraph test_qgsgraph.py)
ADD_PYTHON_TEST(PyQgsGroupLayer test_qgsgrouplayer.py)
ADD_PYTHON_TEST(PyQgsHashLineSymbolLayer test_qgshashlinesymbollayer.py)
ADD_PYTHON_TEST(PyQgsHighlight test_qgshighlight.py)
ADD_PYTHON_TEST(PyQgsHistoryProviderRegistry test_qgshistoryproviderregistry.py)
ADD_PYTHON_TEST(PyQgsImageCache test_qgsimagecache.py)
ADD_PYTHON_TEST(PyQgsImageSourceLineEdit test_qgsimagesourcelineedit.py)
ADD_PYTHON_TEST(PyQgsInterpolatedLineSymbolLayer test_qgsinterpolatedlinesymbollayers.py)

View File

@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsHistoryProviderRegistry.
.. note:: 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.
"""
__author__ = 'Nyall Dawson'
__date__ = '18/10/2016'
__copyright__ = 'Copyright 2016, The QGIS Project'
import qgis # NOQA switch sip api
from qgis.PyQt import sip
from qgis.PyQt.QtCore import (
QDateTime,
QDate
)
from qgis.gui import (
QgsHistoryEntry,
QgsHistoryProviderRegistry,
QgsAbstractHistoryProvider
)
from qgis.testing import start_app, unittest
start_app()
class TestHistoryProvider(QgsAbstractHistoryProvider):
def id(self) -> str:
return 'test_provider'
class TestHistoryProvider2(QgsAbstractHistoryProvider):
def id(self) -> str:
return 'test_provider2'
class TestQgsHistoryProviderRegistry(unittest.TestCase):
def test_entry(self):
"""
Test QgsHistoryEntry
"""
entry = QgsHistoryEntry('my provider', QDateTime(2021, 1, 2, 3, 4, 5), {'somevar': 5})
self.assertEqual(entry.providerId, 'my provider')
self.assertEqual(entry.timestamp, QDateTime(2021, 1, 2, 3, 4, 5))
self.assertEqual(entry.entry, {'somevar': 5})
self.assertEqual(str(entry), '<QgsHistoryEntry: my provider 2021-01-02T03:04:05>')
entry = QgsHistoryEntry({'somevar': 7})
self.assertFalse(entry.providerId)
self.assertEqual(entry.timestamp.date(), QDateTime.currentDateTime().date())
self.assertEqual(entry.entry, {'somevar': 7})
def test_registry_providers(self):
"""
Test registering providers
"""
reg = QgsHistoryProviderRegistry(useMemoryDatabase=True)
self.assertFalse(reg.providerIds())
provider1 = TestHistoryProvider()
provider2 = TestHistoryProvider2()
self.assertTrue(reg.addProvider(provider1))
self.assertEqual(reg.providerIds(), ['test_provider'])
# already registered
self.assertFalse(reg.addProvider(provider1))
self.assertTrue(reg.addProvider(provider2))
self.assertCountEqual(reg.providerIds(), ['test_provider', 'test_provider2'])
self.assertFalse(reg.removeProvider('x'))
self.assertTrue(reg.removeProvider('test_provider'))
self.assertTrue(sip.isdeleted(provider1))
self.assertEqual(reg.providerIds(), ['test_provider2'])
def test_registry_entries(self):
"""
Test storing and retrieving registry entries
"""
reg = QgsHistoryProviderRegistry(useMemoryDatabase=True)
self.assertFalse(reg.queryEntries())
reg.addEntry('my provider', {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}},
QgsHistoryProviderRegistry.HistoryEntryOptions())
reg.addEntry('my provider 2', {'some var': 6},
QgsHistoryProviderRegistry.HistoryEntryOptions())
self.assertEqual(len(reg.queryEntries()), 2)
self.assertEqual(reg.queryEntries()[0].providerId, 'my provider')
self.assertEqual(reg.queryEntries()[0].timestamp.date(), QDateTime.currentDateTime().date())
self.assertEqual(reg.queryEntries()[0].entry, {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}})
self.assertEqual(reg.queryEntries()[1].providerId, 'my provider 2')
self.assertEqual(reg.queryEntries()[1].timestamp.date(), QDateTime.currentDateTime().date())
self.assertEqual(reg.queryEntries()[1].entry, {'some var': 6})
entry = QgsHistoryEntry('my provider 3', QDateTime(2021, 1, 2, 3, 4, 5), {'var': 7})
reg.addEntry(entry)
self.assertEqual(len(reg.queryEntries()), 3)
self.assertEqual(reg.queryEntries()[0].providerId, 'my provider')
self.assertEqual(reg.queryEntries()[0].timestamp.date(), QDateTime.currentDateTime().date())
self.assertEqual(reg.queryEntries()[0].entry, {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}})
self.assertEqual(reg.queryEntries()[1].providerId, 'my provider 2')
self.assertEqual(reg.queryEntries()[1].timestamp.date(), QDateTime.currentDateTime().date())
self.assertEqual(reg.queryEntries()[1].entry, {'some var': 6})
self.assertEqual(reg.queryEntries()[2].providerId, 'my provider 3')
self.assertEqual(reg.queryEntries()[2].timestamp.date(), QDate(2021, 1, 2))
self.assertEqual(reg.queryEntries()[2].entry, {'var': 7})
# query by provider
entries = reg.queryEntries(providerId='my provider')
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].providerId, 'my provider')
self.assertEqual(entries[0].timestamp.date(), QDateTime.currentDateTime().date())
self.assertEqual(entries[0].entry, {'some var': 5, 'other_var': [1, 2, 3], 'final_var': {'a': 'b'}})
entries = reg.queryEntries(providerId='my provider 2')
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].providerId, 'my provider 2')
self.assertEqual(entries[0].timestamp.date(), QDateTime.currentDateTime().date())
self.assertEqual(entries[0].entry, {'some var': 6})
# query by date
entries = reg.queryEntries(start=QDateTime(3022, 1, 2, 3, 4, 5)) # this test will break in 3022, sorry
self.assertEqual(len(entries), 0)
entries = reg.queryEntries(end=QDateTime(2020, 1, 2, 3, 4, 5))
self.assertEqual(len(entries), 0)
entries = reg.queryEntries(start=QDateTime(2021, 3, 2, 3, 4, 5))
self.assertEqual(len(entries), 2)
self.assertCountEqual([e.providerId for e in entries], ['my provider', 'my provider 2'])
entries = reg.queryEntries(end=QDateTime(2021, 3, 2, 3, 4, 5))
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0].providerId, 'my provider 3')
if __name__ == '__main__':
unittest.main()