Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic plugins framework #5186

Merged
merged 17 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ set(QFIELD_CORE_SRCS
networkreply.cpp
orderedrelationmodel.cpp
peliasgeocoder.cpp
pluginmanager.cpp
resourcesource.cpp
printlayoutlistmodel.cpp
projectinfo.cpp
Expand Down Expand Up @@ -189,6 +190,7 @@ set(QFIELD_CORE_HDRS
networkreply.h
orderedrelationmodel.h
peliasgeocoder.h
pluginmanager.h
resourcesource.h
printlayoutlistmodel.h
projectinfo.h
Expand Down
59 changes: 59 additions & 0 deletions src/core/appinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <QDirIterator>
#include <QFileInfo>
#include <QImageReader>
#include <QQuickItem>
#include <qgsapplication.h>
#include <qgsmessagelog.h>
#include <qgsproject.h>
Expand All @@ -40,6 +41,64 @@ AppInterface::AppInterface( QgisMobileapp *app )
{
}

QObject *AppInterface::findItemByObjectName( const QString &name ) const
{
if ( !mApp->rootObjects().isEmpty() )
{
return mApp->rootObjects().at( 0 )->findChild<QObject *>( name );
}
return nullptr;
}

void AppInterface::addItemToPluginsToolbar( QQuickItem *item ) const
{
if ( !mApp->rootObjects().isEmpty() )
{
QQuickItem *toolbar = mApp->rootObjects().at( 0 )->findChild<QQuickItem *>( QStringLiteral( "pluginsToolbar" ) );
item->setParentItem( toolbar );
}
}

void AppInterface::addItemToMainMenuActionsToolbar( QQuickItem *item ) const
{
if ( !mApp->rootObjects().isEmpty() )
{
QQuickItem *toolbar = mApp->rootObjects().at( 0 )->findChild<QQuickItem *>( QStringLiteral( "mainMenuActionsToolbar" ) );
item->setParentItem( toolbar );

// Place the item to the left of the Undo/Redo buttons
const QList<QQuickItem *> childItems = toolbar->childItems();
item->stackBefore( childItems.at( childItems.size() - 3 ) );
}
}

void AppInterface::addItemToCanvasActionsToolbar( QQuickItem *item ) const
{
if ( !mApp->rootObjects().isEmpty() )
{
QQuickItem *toolbar = mApp->rootObjects().at( 0 )->findChild<QQuickItem *>( QStringLiteral( "canvasMenuActionsToolbar" ) );
item->setParentItem( toolbar );
}
}

QObject *AppInterface::mainWindow() const
{
if ( !mApp->rootObjects().isEmpty() )
{
return mApp->rootObjects().at( 0 );
}
return nullptr;
}

QObject *AppInterface::mapCanvas() const
{
if ( !mApp->rootObjects().isEmpty() )
{
return mApp->rootObjects().at( 0 )->findChild<QObject *>( "mapCanvas" );
}
nirvn marked this conversation as resolved.
Show resolved Hide resolved
return nullptr;
}

void AppInterface::removeRecentProject( const QString &path )
{
return mApp->removeRecentProject( path );
Expand Down
31 changes: 31 additions & 0 deletions src/core/appinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
class QgisMobileapp;
class QgsRectangle;
class QgsFeature;
class QQuickItem;

class AppInterface : public QObject
{
Expand Down Expand Up @@ -94,6 +95,36 @@ class AppInterface : public QObject
*/
Q_INVOKABLE void clearProject() const;

/**
* Returns the item matching the provided object \a name
*/
Q_INVOKABLE QObject *findItemByObjectName( const QString &name ) const;

/**
* Adds an \a item in the plugins toolbar container
*/
Q_INVOKABLE void addItemToPluginsToolbar( QQuickItem *item ) const;

/**
* Adds an \a item in the main menu action toolbar container
*/
Q_INVOKABLE void addItemToMainMenuActionsToolbar( QQuickItem *item ) const;

/**
* Adds an \a item in the main menu action toolbar container
*/
Q_INVOKABLE void addItemToCanvasActionsToolbar( QQuickItem *item ) const;

/**
* Returns the main window.
*/
Q_INVOKABLE QObject *mainWindow() const;

/**
* Returns the main map canvas.
*/
Q_INVOKABLE QObject *mapCanvas() const;
nirvn marked this conversation as resolved.
Show resolved Hide resolved

static void setInstance( AppInterface *instance ) { sAppInterface = instance; }
static AppInterface *instance() { return sAppInterface; }

Expand Down
136 changes: 136 additions & 0 deletions src/core/pluginmanager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/***************************************************************************
pluginmanager.h - PluginManager

---------------------
begin : 14.05.2024
copyright : (C) 2024 by Mathieu Pellerin
email : mathieu (at) opengis.ch
***************************************************************************
* *
* 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 "pluginmanager.h"

#include <QDir>
#include <QFileInfo>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QQmlContext>
#include <QSettings>
#include <qgsmessagelog.h>

PluginManager::PluginManager( QQmlEngine *engine )
: QObject( engine )
, mEngine( engine )
{
connect( mEngine, &QQmlEngine::warnings, this, &PluginManager::handleWarnings );
}

void PluginManager::loadPlugin( const QString &pluginPath, const QString &pluginName, bool skipPermissionCheck )
nirvn marked this conversation as resolved.
Show resolved Hide resolved
nirvn marked this conversation as resolved.
Show resolved Hide resolved
{
if ( !skipPermissionCheck )
{
QSettings settings;
settings.beginGroup( QStringLiteral( "/qfield/plugins/%1" ).arg( pluginPath ) );
const QStringList keys = settings.childKeys();
if ( keys.contains( QStringLiteral( "permissionGranted" ) ) )
{
if ( !settings.value( QStringLiteral( "permissionGranted" ) ).toBool() )
{
return;
}
}
else
{
mPermissionRequestPluginPath = pluginPath;
emit pluginPermissionRequested( pluginName );
return;
}
}

if ( mLoadedPlugins.contains( pluginPath ) )
{
unloadPlugin( pluginPath );
}

QQmlComponent component( mEngine, pluginPath, this );
if ( component.status() == QQmlComponent::Status::Error )
{
for ( const QQmlError &error : component.errors() )
{
QgsMessageLog::logMessage( error.toString(), QStringLiteral( "Plugin Manager" ), Qgis::MessageLevel::Critical );
}
return;
}
QObject *object = component.create( mEngine->rootContext() );
mLoadedPlugins.insert( pluginPath, QPointer<QObject>( object ) );
}

void PluginManager::unloadPlugin( const QString &pluginPath )
{
if ( mLoadedPlugins.contains( pluginPath ) )
{
if ( mLoadedPlugins[pluginPath] )
nirvn marked this conversation as resolved.
Show resolved Hide resolved
{
mLoadedPlugins[pluginPath]->deleteLater();
m-kuhn marked this conversation as resolved.
Show resolved Hide resolved
mLoadedPlugins.remove( pluginPath );
}
}
}

void PluginManager::handleWarnings( const QList<QQmlError> &warnings )
{
for ( const QQmlError &warning : warnings )
{
if ( warning.url().isLocalFile() )
{
if ( mLoadedPlugins.keys().contains( warning.url().toLocalFile() ) )
{
QgsMessageLog::logMessage( warning.toString(), QStringLiteral( "Plugin Manager" ), Qgis::MessageLevel::Warning );
}
}
}
}

void PluginManager::grantRequestedPluginPermission( bool permanent )
{
if ( permanent )
{
QSettings settings;
settings.beginGroup( QStringLiteral( "/qfield/plugins/%1" ).arg( mPermissionRequestPluginPath ) );
settings.setValue( QStringLiteral( "permissionGranted" ), true );
settings.endGroup();
}

loadPlugin( mPermissionRequestPluginPath, QString(), true );
mPermissionRequestPluginPath.clear();
}

void PluginManager::denyRequestedPluginPermission( bool permanent )
{
if ( permanent )
{
QSettings settings;
settings.beginGroup( QStringLiteral( "/qfield/plugins/%1" ).arg( mPermissionRequestPluginPath ) );
settings.setValue( QStringLiteral( "permissionGranted" ), false );
settings.endGroup();
}

mPermissionRequestPluginPath.clear();
}

QString PluginManager::findProjectPlugin( const QString &projectPath )
{
const QFileInfo fi( projectPath );
nirvn marked this conversation as resolved.
Show resolved Hide resolved
const QString pluginPath = QStringLiteral( "%1/%2.qml" ).arg( fi.absolutePath(), fi.completeBaseName() );
if ( QFileInfo::exists( pluginPath ) )
{
return pluginPath;
nirvn marked this conversation as resolved.
Show resolved Hide resolved
}
return QString();
}
52 changes: 52 additions & 0 deletions src/core/pluginmanager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/***************************************************************************
pluginmanager.h - PluginManager

---------------------
begin : 14.05.2024
copyright : (C) 2024 by Mathieu Pellerin
email : mathieu (at) opengis.ch
***************************************************************************
* *
* 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 PLUGINMANAGER_H
#define PLUGINMANAGER_H

#include <QObject>
#include <QQmlEngine>

class PluginManager : public QObject
nirvn marked this conversation as resolved.
Show resolved Hide resolved
{
Q_OBJECT

public:
explicit PluginManager( QQmlEngine *engine );
~PluginManager() override = default;

void loadPlugin( const QString &pluginPath, const QString &pluginName, bool skipPermissionCheck = false );
void unloadPlugin( const QString &pluginPath );

Q_INVOKABLE void grantRequestedPluginPermission( bool permanent = false );
Q_INVOKABLE void denyRequestedPluginPermission( bool permanent = false );

static QString findProjectPlugin( const QString &projectPath );

signals:
void pluginPermissionRequested( const QString &pluginName );

private slots:
void handleWarnings( const QList<QQmlError> &warnings );

private:
nirvn marked this conversation as resolved.
Show resolved Hide resolved
QQmlEngine *mEngine = nullptr;
QMap<QString, QPointer<QObject>> mLoadedPlugins;

QString mPermissionRequestPluginPath;
};

#endif // PLUGINMANAGER_H