8.1. Extending features with plug-ins
Plug-ins are the best way to add a specific set of features to the software without changing its core code. pgModeler has a simple structure that provides the means to create third-party plug-ins. The first step in the development of a plug-in that handles pgModeler internals is to study pgModeler's API as well as the Qt plug-in system. You can generate pgModeler's code documentation by using the doxygen utility, running the command doxygen doxygen.conf at pgModeler's source code root. Once generated, the code documentation is stored in the folder docs under the source code directory.
Details about configuring and installing the doxygen application can be seen on its official website. Steps on how to retrieve the source code of pgModeler can be seen on the download page. Once you have the basic information about plug-ins, you can start to create your own extended features.
In the Qt framework, and consequently, in pgModeler, a plug-in is basically a C++ class implemented in a library installed in a path that is accessible by the application. The C++ class that implements the plug-in must follow two basic rules: inherit the class PgModelerGuiPlugin (plug-ins for the graphical interface) or PgModelerCliPlugin (plug-ins for the command-line interface), implementing all its pure virtual methods, and use the macros Q_INTERFACES and Q_PLUGIN_METADATA in its declaration. Another rule is that the plug-ins in pgModeler must have a basic directory structure to be properly loaded when the application starts. In the source code tree, a plug-in is a folder of the subproject plugins that has the following structure:
[PGMODELER_SOURCE_ROOT]/plugins/
|
+ - pluginA/
|
+ ---- CMakeLists.txt (build instructions)
|
+ ---- src/ (sources: pluginA.h, pluginA.cpp)
|
+ ---- res/
| |
| +--- pluginA.qrc (resource file)
| |
| +--- /pluginA (context)
| |
| +--- pluginA.png (icon)
|
+ ---- lang/ (optional, translation dictionaries: [language].ts)
Once built and installed, the directory used by pgModeler to load plug-ins has the following layout:
[PGMODELER_PLUGINS_DIR]/
|
+ - pluginA/
|
+ ---- (lib)pluginA.(so|dylib|dll) (library, with the resources compiled into it)
|
+ ---- lang/ (optional, compiled dictionaries: [language].qm)
|
+ ---- (other files installed by the plug-in: settings, schemas, etc)
On the first schema, the directory named pluginA stores all the assets and components of the extension. Inside that folder, there is a res subdirectory in which the icons used by the plug-in are registered in a resource file (.qrc), the src subdirectory with the plug-in's code, and an optional lang subdirectory with the dictionaries used to translate the plug-in's interface. On the second schema, the folder pluginA is located in the root directory used by pgModeler to load plug-ins, and holds the library that implements all the plug-in's logic, with the resources compiled into it. The name of the library is platform-dependent and can be: libpluginA.so (Linux), pluginA.dll (Windows), and libpluginA.dylib (macOS).
Since the plug-in needs to access the tool's features exposed by its classes, plug-ins are built together with pgModeler, using its build system. The next subsection explains the process.
Building plug-ins
Since version 2.0, pgModeler is built with CMake, and so are its plug-ins: the plug-ins are subprojects built together with the tool, by the same build process that generates the entire tool. The folder plugins, at the source code root, holds the plug-ins of the Community Edition: sampleplugin, an example of a graphical plug-in, and samplecliplugin, an example of a plug-in for the command-line interface. Each plug-in folder has a CMakeLists.txt that uses the helper functions defined in the file PluginBuildConfs.cmake. To create your own plug-in, follow these steps:
-
Download the source code, making sure it is the same version as your pgModeler installation, or the build may fail. The complete steps for the tool's build are available in the installation instructions.
git clone https://github.com/nullptrlabs/pgmodeler.gitThen check out the tag related to the pgModeler installed on your system. Supposing that the version in your system is 2.0.0, the checkout command must be:
git checkout v2.0.0 -
Create the plug-in folder in
plugins/(for instance, by copyingsampleplugin) with the structure described above, and write the plug-in'sCMakeLists.txt. The one below is a good starting point for a graphical plug-in:pgm_add_gui_plugin(pluginA OFF)target_sources(${PGM_TARGET} PRIVATEsrc/pluginA.cpp src/pluginA.h)pgm_set_plugin_res(${PGM_TARGET})pgm_install_plugin(${PGM_TARGET})The function
pgm_add_gui_plugin(orpgm_add_cli_plugin, for command-line plug-ins) creates the plug-in's target;pgm_set_plugin_rescompiles the resource fileres/pluginA.qrcinto the library; andpgm_install_pluginconfigures the installation of the library in the plug-ins root directory and of the optional translations found in thelangfolder. -
Register the new folder in the file
plugins/CMakeLists.txt:add_subdirectory(pluginA) -
Configure, build, and install pgModeler as described in the installation instructions. In short, from the source code root:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Releasecmake --build buildcmake --install build
If the build process succeeds, you will see that some files are installed in the plug-ins root directory of your pgModeler installation. If you don't know where it is, just run pgModeler, open the settings dialog, and locate the Plug-ins settings; the path is presented in the field Plug-ins root directory. Now, just run pgModeler to test if the plug-ins are working properly. If a plug-in has any problem, pgModeler will refuse to load it and present a detailed error dialog. You can use the presented information to ask for help.
pgModeler gives complete access to its classes and doesn't prohibit the plug-ins from reading and writing files outside their paths. Therefore, you must take extreme caution when developing a plug-in or even when installing a plug-in from suspicious sources, since this may harm your computer or cause data loss.
API changes since version 1.2
If you are porting a plug-in written for the 1.2 series, these are the changes of the plug-in interface and of the GUI library that matter most (the complete list is in the file CHANGELOG.md of the source code):
- The build system is CMake on all platforms (see the previous section) and the minimum required Qt version is 6.6.
PgModelerGuiPlugin::PluginWidgetsgained the fieldextra_wgt, an auxiliary widget for general purposes, and the fieldswelcome_wgt_btnandw_wgt_btn_id, which let a plug-in install a button in the Welcome view; the position of the button is chosen with the enumPgModelerGuiPlugin::WelcomeWgtBtnId(NewModelBtn,LoadModelBtn,SampleModelsBtn,RecentModelsBtn,LastSessionBtn,SupportBtn, orAppendBtnto add it at the end).- The method
PgModelerGuiPlugin::getWidgetDockMode()returns aWidgetDockModethat tells where the widgets of the plug-in are installed:DockOnMainWnd(side bar of the main window),DockOnSqlExecWgt(SQL execution panel), orNoDock(not docked, which must be avoided because it can lead to memory leaks). MainWindowhas the new signalss_modelClosed(), emitted when a model is closed, ands_modelLoadRequested(filename, model_idx), emitted when a model must be reloaded (seeMainWindow::reloadModel()), so plug-ins can react to them or intercept the reload. The signals_modelSavedis now emitted only when the model file is actually written to disk.FileSelectorWidgetwas renamed toPathSelectorWidget, since it selects both files and directories; the button members ofBaseFormwere renamed fromapply_ok_btnandcancel_btntoaccept_btnandreject_btn;DebugOutputWidget::setLogMessages()is nowsetLogAppMessages().- The user interface is translatable in every executable, so texts that can't call
tr()where they are declared must use the helpersPGM_TR2andUtilsNs::pgmTr.
References
How to create Qt plug-ins
http://doc.qt.io/qt-6/plugins-howto.html
Doxygen utility
http://www.doxygen.nl