All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Pages
Extension System

Overview

Qtilities provides an extension management library which has the following goals and responsibilities:

  • Define how plugins interface with an application.
  • Getting the plugins up and running. This includes plugin loading, initialization and finalization.
  • Provide management of plugin configuration sets, that is: Provide control over which plugins must be loaded and initialized.
  • Provide information about the loaded plugins once they are loaded and initialized.

The Qtilities::ExtensionSystem::ExtensionSystemCore singleton provides the interface through which these tasks can be achieved. This article will provide details of how the extension system achieves these goals and give an overview of the different features provided.

Table of contents:

First Steps

Overviews

Goal 1: Define how plugins interface with an application

The Qtilities::ExtensionSystem::Interfaces::IPlugin interface defines the interface between the extension system and plugins it can load. The interface provides status information about the state of a plugin as well as information about the plugin itself and was made as lightweight as possible. Therefore it does not inherit from anything and any object can implement it.

Lets look at a simplified version of the PluginTemplate example in the QtilitiesPlugins project which is a ready to use template implementing this interface. First lets look at the header file:

class PluginTemplate : public QObject, public IPlugin
{
Q_OBJECT
public:
PluginTemplate(QObject* parent = 0);
~PluginTemplate() {}
// --------------------------------
// IObjectBase Implementation
// --------------------------------
QObject* objectBase() { return this; }
const QObject* objectBase() const { return this; }
// --------------------------------------------
// IPlugin Implementation
// --------------------------------------------
bool initialize(const QStringList &arguments, QStringList *error_strings);
bool initializeDependencies(QStringList *error_strings);
void finalize() {}
QString pluginName() const { return "Example Plugin"; }
QtilitiesCategory pluginCategory() const { return QtilitiesCategory("General"); }
VersionInformation pluginVersionInformation() const;
QString pluginPublisher() const;
QString pluginPublisherWebsite() const;
QString pluginPublisherContact() const;
QString pluginDescription() const;
QString pluginCopyright() const;
QString pluginLicense() const;
};

Next we look at the .cpp implementation file:

PluginTemplate::PluginTemplate(QObject* parent) : QObject(parent) {
setObjectName(pluginName());
}
bool PluginTemplate::initialize(const QStringList &arguments, QStringList *error_strings) {
Q_UNUSED(arguments)
Q_UNUSED(error_strings)
return true;
}
bool PluginTemplate::initializeDependencies(QStringList *error_strings) {
Q_UNUSED(error_strings)
return true;
}
Qtilities::Core::VersionInformation PluginTemplate::pluginVersionInformation() const {
VersionInformation version_info(1,0,0);
return version_info;
}
QString PluginTemplate::pluginPublisher() const {
return "Jaco Naudé";
}
QString PluginTemplate::pluginPublisherWebsite() const {
return "http://www.qtilities.org";
}
QString PluginTemplate::pluginPublisherContact() const {
return "support@qtilities.org";
}
QString PluginTemplate::pluginDescription() const {
return tr("An example Qtilities Extension System plugin.");
}
QString PluginTemplate::pluginCopyright() const {
return QString(tr("Copyright") + " 2010, Jaco Naudé");
}
QString PluginTemplate::pluginLicense() const {
return tr("See the Qtilities Libraries license");
}
Q_EXPORT_PLUGIN2(PluginTemplate, PluginTemplate);

The implementation above shows that it is very easy to implement the interface and the best place to start is to use Qtilities::Plugins::Template.

Goal 2: Getting the plugins up and running

Loading plugins

The Qtilities::ExtensionSystem::ExtensionSystemCore::initialize() function loads plugins from path(s) defined in Qtilities::ExtensionSystem::ExtensionSystemCore::pluginPaths(). By default all plugins in the EXECUTABLE_PATH/plugin directory are loaded and new paths can be added using the Qtilities::ExtensionSystem::ExtensionSystemCore::addPluginPath() function.

During the loading process, the extension system core class emits the newProcessMessage() signal with progress messages which can be connected to a splash window if necessary (see the Qtilities::Examples::MainWindow example which demonstrates this for more information).

The example below shows how to use the extension system to load plugins in your application's main() function.

#include <QtGui>
#include <QtilitiesExtensionSystem>
using namespace QtilitiesCore;
using namespace QtilitiesCoreGui;
using namespace QtilitiesExtensionSystem;
int main(int argc, char *argv[])
{
QtilitiesApplication a(argc, argv);
// We must specify the following if we want the logger to remember its settings:
QtilitiesApplication::setOrganizationName("Jaco Naudé");
QtilitiesApplication::setOrganizationDomain("Qtilities");
QtilitiesApplication::setApplicationName("Extension System Example");
QtilitiesApplication::setApplicationVersion("0.1");
// Initialize the logger:
QtilitiesApplication::applicationSessionPath();
LOG_INITIALIZE();
// Set the Qt message engine active, this will log messages to the Qt debugging system during the plugin loading process:
Log->toggleQtMsgEngine(true);
// Load plugins using the extension system:
EXTENSION_SYSTEM->initialize();
// Set the Qt message engine inactive once the process loading process is completed:
Log->toggleQtMsgEngine(false);
// Register extension system config page:
OBJECT_MANAGER->registerObject(EXTENSION_SYSTEM->configWidget());
// Report on the number of config pages found:
QList<QObject*> registered_config_pages = OBJECT_MANAGER->registeredInterfaces("com.Qtilities.CoreGui.IConfigPage/1.0");
LOG_INFO(QString("%1 configuration page(s) found in set of loaded plugins.").arg(registered_config_pages.count()));
// Create a config widget for this example:
ConfigurationWidget config_widget;
config_widget.initialize(registered_config_pages);
exampleMainWindow->show();
int result = a.exec();
// Finalize all plugins:
EXTENSION_SYSTEM->finalize();
// Finalize the logger:
LOG_FINALIZE();
return result;
}

The above example registers the Qtilities::ExtensionSystem::PluginInfoWidget widget in the global object pool which is discussed in the Goal 3: Provide plugin information at runtime section.

Plugin initialization and finalization

The diagram below shows the sequence in which plugins are initialized during the plugin loading process.

plugin_loading_sequence.jpg
Plugin Loading Sequence

Plugins should register any objects in which other plugins or the main application might be interested in (configuration pages, contexts) etc. in the Qtilities::ExtensionSystem::Interfaces::IPlugin::initialize() function implementation. After all plugin instances were created and added to the global object pool, this function is called on all plugins. That is, all plugins are loaded first before any of them are initialized. To make things clear, lets look at simplified version of the initialize() function implementation in the Qtilities::Plugins::SessionLog plugin:

bool Qtilities::Plugins::SessionLog::SessionLogPlugin::initialize(const QStringList &arguments, QStringList *error_strings) {
Q_UNUSED(arguments)
Q_UNUSED(error_strings)
// Add the session log mode to the global object pool:
SessionLogMode* session_log_mode = new SessionLogMode();
OBJECT_MANAGER->registerObject(session_log_mode);
// Register the context of the session log mode:
CONTEXT_MANAGER->registerContext(session_log_mode->contextString());
// Register the logging configuration widget:
OBJECT_MANAGER->registerObject(LoggerGui::createLoggerConfigWidget(false));
return true;
}

This is followed by calling the Qtilities::ExtensionSystem::Interfaces::IPlugin::initializeDependencies() function on all plugins. In this function plugins can inspect the global object pool to for interfaces they are interested in using the Qtilities::Core::Interfaces::IObjectManager::registeredInterfaces() function. Again, to make things clear lets write an implementation of the initializeDependencies() function which looks for all projects parts registered in the global object pool:

bool ProjectPartFinderPlugin::initializeDependencies(QStringList *error_strings) {
Q_UNUSED(error_strings)
// Get a list of all the project items in the object pool:
QList<QObject*> projectItemObjects = OBJECT_MANAGER->registeredInterfaces("com.Qtilities.ProjectManagement.IProjectItem/1.0");
QList<IProjectItem*> projectItems;
// Cast all items:
for (int i = 0; i < projectItemObjects.count(); i++) {
IProjectItem* part = qobject_cast<IProjectItem*> (projectItemObjects.at(i));
if (part)
projectItems.append(part);
}
// Now we will have a list with all the project items in the global object pool.
return true;
}

Goal 3: Provide plugin information at runtime

The main access point to information about loaded plugins is the extension system configuration widget class (Qtilities::ExtensionSystem::ExtensionSystemConfig). The configuration widget is shown below:

extension_system_configuration_widget.jpg
Extension System Widget Plugin Overview

The "Plugin Paths" tab in the configuration widget provides details about the paths searched for plugins, thus it shows the paths returned by Qtilities::ExtensionSystem::ExtensionSystemCore::pluginPaths(). For each plugin we can get detailed information by double clicking its name, or by clicking the "Details" button. The image below shows the detailed information of one of the provided Qtilities plugins.

extension_system_details_widget.jpg
Plugin Details

Goal 4: Provide management of plugin configuration sets

In advanced applications it is often desirable to specify a set of plugins which should be loaded for different scenarios. In Qtilities we call a set of plugins that must be loaded for a specific scenario a plugin configuration set.

Plugin Classification

The extension system allow management of different configuration sets which groups plugins into the following groups:

A configuration set gives the names of the plugins which falls into the inactive and filtered plugin groups. When a plugin is found during the Qtilities::ExtensionSystem::ExtensionSystemCore::initialize() function call, the plugin is checked agains the information provided by the configuration set and handled accordingly. When a plugin is found which is not categorized in the configuration set, it automatically falls under the Active Plugins category. In the same way, if a plugin occurs under more than one category in the configuration set, it automatically falls under the Active Plugins set.

The Qtilities::ExtensionSystem::ExtensionSystemCore class provides different functions to get information about the plugins that fell into the different categories during initialization.

Creating Configuration Sets

Creating configuration sets is easy and flexible. The important thing to remember is: The plugin configuration set that will be used is the one that is active at the time that the extension system's initialize() function is called. There are different ways to set it up:

Below is an example plugin configuration set file:

<!DOCTYPE QtilitiesPluginConfiguration>
<QtilitiesPluginConfiguration QtilitiesVersion="0.3.0" ExportVersion="0">
<InactivePlugins>
<PluginName Value="Test Plugin 1"/>
<PluginName Value="Test Plugin 2"/>
<PluginName Value="Test Plugin 3"/>
</InactivePlugins>
<FilteredPlugins>
<Expression Value="Filter1*"/>
<Expression Value="Filter2*"/>
<Expression Value="Filter3*"/>
</FilteredPlugins>
</QtilitiesPluginConfiguration>

Allowing Users Control Over Sets

The developer will have a specific way in mind that he or she wants to manage configuration sets their of application. To accomodate different scenarios the way that sets are handled can be customized through the ExtensionSystemCore class. It is for example possible to define some core plugins and then let the user decide which plugins they want to be active and incative.

The plugin details widget can either show the current active and inactive plugins without allowing the user to change it, or it can give full control to the user. To show the activity of plugins and to allow the users to control the activity of plugins, use the following functions on the extension system:

EXTENSION_SYSTEM->enablePluginActivityDisplay();
EXTENSION_SYSTEM->enablePluginActivityControl();

Below is an example of the plugin configuration widget with the above two activity options enabled and with the "Session Log Plugin" specified to be a core plugin (thus it is greyed out to indicate this and a proper message is displayed when an attempt is made to deactivate it). The image shows the state of the widget just after the user disabled the "Project Management Plugin".

extension_system_widget_with_config_control.jpg
Plugin Configuration Set Display

When the user is given control over plugin configuration sets, changing the current set will automatically save the new configuration to the file name of the loaded set. By default this is App_Path/session/default.pconfig. However if a valid plugin configuration has been loaded using the Qtilities::ExtensionSystem::ExtensionSystemCore::loadPluginConfiguration() function, this loaded configuration file will be updated to reflect the changes the user made. The Qtilities::ExtensionSystem::ExtensionSystemCore::setActivePluginConfigurationFile() function can be specified. It is important to note that the extension system's initialize() function loads the active plugin configuration file automatically if it exists. Thus is does not make sense to load the configuration before calling initialize() unless you want to modify it.



Qtilities : Reference Documentation Back to top Copyright © 2009-2013, Jaco Naudé