Creating a New Plugin
This guide explains how to create a new MNE Analyze plugin, including implementing the required virtual functions, setting up GUI elements, and integrating with the event system.
Overview
All MNE Analyze plugins inherit from AbstractPlugin (applications/mne_analyze/libs/anShared/Plugins/abstractplugin.h). Unlike MNE Scan, MNE Analyze does not differentiate between plugin types.
Plugins are loaded at runtime from bin/mne_analyze_plugins. Once loaded, MNE Analyze retrieves each plugin's menus, controls, and views and displays them to the user. Currently each plugin returns at most one of each GUI element.
For implementation details, see:
analyzecore.cpp→initPluginManager()— how plugins are discovered and loadedmainwindow.cppconstructor — how GUI elements are assembled
Sample Plugin
A sample plugin with no functionality is included in applications/mne_analyze/plugins/sampleplugin. To create a new plugin, duplicate that folder and replace all instances of SamplePlugin with your plugin name. See the checklist at the end of this guide for details that are easy to miss.
Deriving from AbstractPlugin
AbstractPlugin has a number of pure virtual functions that need to be defined by any new plugin. Among them are functions for getting the view, control, and menu GUI items for the plugin, as well subscribing to and receiving events from the event manager.
clone()
Returns an instance of the plugin. This is not a copy. Most of the existing plugins do something like this:
return QSharedPointer<AbstractPlugin>(new Averaging);
init()
Initializes the plugin. This gets called after all the plugins get loaded into the Plugin Manager. Any startup actions that need to be done before the user begins interacting with the plugin, such as allocating memory or initializing parameters, can be done here.
unload()
Closes the plugin. Gets called when the plugins are being shutdown when the main window is closed. Any shutdown actions that need to be performed before the program shuts down can be done here.
getName()
Returns the plugin name, this will be used to set the plugin title on any views, controls, and menu items the plugin has.
getMenu()
Returns menu items to be added to the top dropdown menu bar. If the plugin does not need a menu, return a Q_NULLPTR (null pointer) instead.
Create a menu object to return, and populate it with relevant actions. Below is a section of the data loader plugin as an example:
QMenu* pMenuFile = new QMenu(tr("File"));
QAction* pActionLoad = new QAction(tr("Open"));
pActionLoad->setStatusTip(tr("Load a data file"));
connect(pActionLoad, &QAction::triggered,
this, &DataLoader::onLoadFilePressed);
If the name of the menu matches an existing one, the new actions will be added to that existing menu, otherwise a new one will be created.
getControl()
Returns a QDockWidget* containing the control GUI elements of the plugin. If the plugin does not need controls, return a Q_NULLPTR (null pointer) instead. Here we also set the allowed docking areas and the tab name that appears when the plugin is docked.
The controls are set by adding any QWidget to the QDockWidget that needs to be returned. Below is an example with a single widget, the tab name set to the plugin name, docked on the left or right sides of the window:
QDockWidget* pControlDock = new QDockWidget(getName());
pControlDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
pControlDock->setObjectName(getName());
QPushButton* pSingleButton = new QPushButton("Compute");
pControlDock->setWidget(pSingleButton);
This is useful if the control widget is already created. If instead the control widget needs to be created or multiple widgets need to be added, a layout can be used:
QDockWidget* pControlDock = new QDockWidget(getName());
pControlDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
pControlDock->setObjectName(getName());
QScrollArea* wrappedScrollArea = new QScrollArea(pControlDock);
QVBoxLayout* pLayout = new QVBoxLayout;
pControlDock->setWidget(wrappedScrollArea);
QPushButton* pFirstButton = new QPushButton("Compute");
QPushButton* pSecondButton = new QPushButton("Clear");
pLayout->addWidget(pFirstButton);
pLayout->addWidget(pSecondButton);
Many control widgets are already implemented in the Disp library and can be reused here.
To influence the ordering of your plugin's controls in the "Control" drop-down menu, modify the m_iOrder variable. The default is 0; higher values place the controls higher in the menu. Negative values force controls to appear lower.
getView()
Returns a QWidget* with the view GUI element of the plugin. If the plugin does not need a view, return a Q_NULLPTR (null pointer) instead.
In all cases in MNE Analyze the view is created elsewhere and returned here as an instance. Many view widgets are already implemented in the Disp library and can be reused.
getEventSubscriptions()
Returns a QVector<EVENT_TYPE> with the events the plugin is subscribed to. If the plugin does not care about incoming events, return an empty QVector. See example below:
QVector<EVENT_TYPE> temp;
temp.push_back(SELECTED_MODEL_CHANGED);
temp.push_back(FILTER_ACTIVE_CHANGED);
temp.push_back(FILTER_DESIGN_CHANGED);
handleEvent()
Receives event e that the plugin is subscribed to. Typically this is handled by a switch-case for each subscribed event:
switch (e->getType()) {
case EVENT_TYPE::SELECTED_MODEL_CHANGED:
// do something
break;
case FILTER_ACTIVE_CHANGED:
// do something
break;
case FILTER_DESIGN_CHANGED:
// do something
break;
default:
qWarning() << "[Averaging::handleEvent] Received an Event that is not handled by switch cases.";
}
Views and Controls
Existing views and controls live in the Disp library and can be reused when building your plugin's GUI. Custom widgets can be created with Qt Designer (.ui files). All views and controls subclass AbstractView.
Using the Event System
Events are how MNE Analyze plugins communicate. Subscribe to events to receive notifications about loaded data, selected items, or scaling changes. Send events to broadcast data or trigger actions in other plugins. See the Event System page for details.
Setting and Clearing Data
Data is displayed using views that present the contents of models, following Qt's Model/View architecture.
Static Building
When building statically, MNE Analyze needs to be told about the plugins at build time. To do this, the plugins are included with Q_IMPORT_PLUGIN in applications/mne_analyze/mne_analyze/main.cpp under the #ifdef STATICBUILD macro. The CMake configuration handles the rest of the static build setup automatically.
Checklist
Before submitting your plugin as a pull request, verify:
- Plugin added to the
CMakeLists.txtin the plugins directory - Plugin's own
CMakeLists.txthas correct target name, source files, and linked libraries - Plugin has a
*_global.hfile with export macros - All
AbstractPluginvirtual functions are implemented -
Q_IMPORT_PLUGINadded inmain.cppfor static builds - Plugin compiles and loads successfully in MNE Analyze