Welcome to CCS UI Framework’s documentation!

1. Widget Module Structure

The following is the expected contents of a widgets library:

libraryname
|
|- widgets                         // only widgets, just drawing instructions
|  |- src
|  |  |- include
|  |     |- libraryname.h          // includes all other widgets headers
|  |     \- libraryname
|  |        |- widgetnameone.hpp
|  |        \- widgetnametwo.hpp
|  |  |- widgetnameone.cpp         // inherits at some point from QWidget
|  |  |- widgetnametwo.cpp         // inherits at some point from QWidget
|  |  |- uifileifneeded.ui
|  |  \- qrcfileifneeded.qrc
|  |- resource
|  |  \- image.png
|  \- wscript
|
|- plugin
|  |- src
|     |- include
|        |- widgetnameoneplugin.hpp // inherits from QDesignerCustomWidgetInterface
|        |- widgetnametwoplugin.hpp // inherits from QDesignerCustomWidgetInterface
|        |- librarynameplugin.hpp
|     |- widgetnameoneplugin.cpp
|     |- widgetnametwoplugin.cpp
|     \- librarynameplugin.cpp      // inherits from QDesignerCustomWidgetCollectionInterface
|  |- plugin.json                   // empty
|  \- wscript
|
\- wscript

The project shall be called “libraryname”. It is also how the module for this library is called. The project will compile two products:

  • 1 shared library, with the widgets.

  • 1 shared library, that is a plugin, for Qt Designer.

Tip

When developing widgets, you can forgo for a while the plugin. You can use Widget Promotion in the Qt Designer in the meantime. But in order to allow other developers to design Uis using the widgets, you need to provide a plugin for the Designer.

Warning

Please do not bundle the widget and the plugin together in one WAF module.

2. Widgets Module

The “widgets” module, through its WAF script will compile a shared library. An example of this:

from wtools.module import declare_qt5cshlib

declare_qt5cshlib( target='widgets',
                   use='Qt5Core Qt5Gui Qt5Widgets Qt5Svg Qt5Designer Qt5Xml'
)

The declare_qt5cshlib command will automatically search for any UI files, and produce the necessary UIC instruction, creating the C++ file for that UI file. Also, when installing this library, its target will be $INTROOT/lib64, which is already in the LD_LIBRARY_PATH, and therefore your program will be able to find it. Also, this will install the includes, so that developer can find them in $INTROOT/include

Warning

This WAF command does not trigger the call to the MOC compiler automatically (See WAF Qt5 documentation). When a Class inherits from QObject, the developer needs to add to its widgetname.cpp file these lines:

#if WAF
#include "include/widgetnameone.moc"
#endif

3. Plugin Module

In the “plugin” module, the wscript will look a bit different:

from wtools.module import declare_qt5cshlib

declare_qt5cshlib( target='plugin',
                   use='Qt5Core Qt5Gui Qt5Widgets Qt5Designer Qt5Xml Qt5UiPlugin libraryname.widgets',
                   qt5_plugin=True
)

This wscript file also uses the declare_qt5cshlib wtools instruction. The main difference here is the additional keyword argument qt5_plugin=True. This will indicate to the install command that the resulting library should be place into $INTROOT/lib64/designer. Then the QT_PLUGIN_PATH environment variable can be used by Qt Designer to find any widgets plugin. QT_PLUGIN_PATH shall be $INTROOT/lib64, as the designer expects a certain directory structure. One of these directories is designer, which is the one used to store any plugin intended for Qt Designer.

The second different is that the use argument for declare_qt5cshlib command now include libraryname.widgets. This indicates WAF that widgets module is a depedency, and therefore, must be compiled before this module, its includes added to include paths, and its library added for linking instructions.

Also, the installation instruction does not copy the includes from this module to the $INTROOT. As the plugins are not intended to be used for development, these files are not meant for further development.

4. Bindings Module

The bindings module has no WAF support at this point, but this is the expected structure. The product of this module is a C++ shared library that acts as a python module. Its installation target is different, as it is intended to be loaded and imported by python scripts: $INTROOT/lib/python$PYTHON_VERSION/site-packages/.

Warning

This module is here for illustration purposes only. When WAF support is added, we will review this document.

This is how the module would look like with bindings added:

libraryname
|
|- widgets
|  |- src
|  |  |- include
|  |     |- libraryname.h           // include all other widget headers
|  |     \- libraryname
|  |        |- widgetnameone.hpp
|  |        \- widgetnametwo.hpp
|  |  |- widgetnameone.cpp
|  |  |- widgetnametwo.cpp
|  |  |- uifileifneeded.ui
|  |  \- qrcfileifneeded.qrc
|  |- resource
|  |  \- image.png
|  \- wscript
|
|- plugin
|  |- src
|     |- include
|        |- widgetnameoneplugin.hpp
|        |- widgetnametwoplugin.hpp
|        |- librarynameplugin.hpp
|     |- widgetnameoneplugin.cpp
|     |- widgetnametwoplugin.cpp
|     \- librarynameplugin.cpp
|  |- plugin.json
|  \- wscript
|
|- bindings
|  |- src
|     |- bindings.hpp
|     \- bindings.xml             // uses "libraryname" as python module name
|  \- wscript
|
\- wscript

5. FAQ

5.1. Why the separation between the widget and plugin library

The Plugin library is only used by Qt Designer. When compiling an application, only the header file and the widgets library themselves are only needed. The Qt Designer Plugins only provides presentation in the Qt Designer for the plugin, and a piece of XML to be included in the UI File. This piece of XML include the header and Class for the include statement.

Also, these library are expected to be found in different places. Qt Designer needs them to be in a designer directory to find them, while the widgets needs to be in LD_LIBRARY_PATH for the application to find them. Certainly one can point LD_LIBRARY_PATH to $INTROOT/lib64/designer, but this is not encouraged.

This way you can also debug and developed modularly the widgets. When you have the widget code ready, then you focus on the plugin aspect of it. Since plugin code is very similar to all plugin, then this is very straightforward. As a recommendation, I would suggest to add a fourth module: a showcase application, that allows you to see and interact with the widgets. Think of it as a “demo”. This showcase application should only link to the widgets module, but its UI file should be constructed using Qt Designer, using the provided plugins. If the plugin is not ready, you can use Widget promotion to transform one widget into another that does not have a plugin.

5.2. The widget plugin does not load

Try using designer command. This will start the Qt Designer in standalone mode. In the main window Menu > Help > Plugins, the application will show you a dialog with information of the plugins found, and why they failed to load. If the plugin is not listed, then the QT_PLUGIN_PATH is not set.:

QT_PLUGIN_PATH=$INTROOT/lib64:$QT_PLUGIN_PATH designer

5.3. What method of library is used for bindings

Shiboken2. At the moment this is not supported by wtools, but support will come soon.

5.4. Why is there a header called “libraryname.h”

While creating python bindings, a module name must be given to the resulting python module (bindings.xml file has this information).

The widgets plugin return through the name() method the name of the class used to create the widget Object. The includeFile() method returns the C++ header file to put into the generated code #include statement.

pyside2-uic –the tool that generates python code from UI file– uses the same methods to generate the code for Python Uis. If the names differs (between the Python bindings module and the C++ include), either the C++ or Python version of the resulting generated code for the UI file would fail.

Example 1: We do not have the libraryname.h file:

  • C++ Class name: widgetnameone

  • C++ header file: widgetnameone.hpp

  • Python Class name: widgetnameone

  • Python module: libraryname

Then, the resulting include and import statements would be:

#include <widgetnameone.hpp>
From widgetnameone.hpp import widgetnameone

pyside2-uic cuts the last two characters from the C++ header file, and uses the name of the file as the module it should include. In this case, the python import fails.

Example 2: We create the libraryname.h file, and include other widgets here:

  • C++ Class name: widgetnameone

  • C++ header file: libraryname.h

  • Python Class name: widgetnameone

  • Python module: libraryname

Then, the resulting include and import statements would be:

#include <libraryname.h>
From libraryname import widgetnameone

5.5. libraryname.h? why not libraryname.hpp?

Code generation in Qt PySide2, only uses names that ends in .h or without any extension. If the file has any other suffix, then the whole string is used. This would cause that under Python, the UIC generated code would look like:

from libraryname.hpp import WidgetNameOne