Extensions can act as QML extensions, but they can be also used like standard libraries.
Directory structure of extensions follows standard QML extensions scheme.
According to QML identified module rules path of an extension relative
to extensions
directory defines module identifier, which is also extension
name denoted as BaseName.MajorVersion. Typically BaseName consists
of dot-separated VendorName.ExtensionName parts, but this is a convention
rather than a requirement. Extension name must only contain base name and
major version suffix. Base name parts have no special meaning.
As a reference we can take Templates.CppSkeleton.0
extension. Fully qualified
name consists of base name (Templates.CppSkeleton
) and major version
suffix (0
). Base name consists of two parts: vendor name (Templates
)
and extension name (CppSkeleton
). It is allowed to omit extension name for a
single extension from a specific vendor. It is also allowed to provide
dot-separated extension name (e.g. Examples.CppSkeleton
); or do the same with
vendor (e.g. org.nokia
).
Extension directories follow camel-case (PascalCase) rules in order to remain consistent with QML extension naming convention used by Qt. On the other hand following this convention within C++ would be inconvenient in many places, so compromises have been made. Sometimes semantic members of extension directory name are lowercased. To express such intention, a pattern VendorName.ExtensionName.MajorVersion will be transformed according to the intention (i.e. vendorname represents lowercased VendorName).
Typically each extension will have directory structure similar to this one.
include
- public header files (available to other modules).- vendorname - for example
templates
.- extensionname - for example
cppskeleton
.internal
- some headers need to be publicly available, but they are implementation detail. This directory is for such files.
- extensionname - for example
- vendorname - for example
src
- private header files and implementation.- vendorname - for example
templates
.- extensionname - for example
cppskeleton
.internal
- corresponds withinternal
subdirectory insideinclude
directory.
- extensionname - for example
- vendorname - for example
tests
- Tests.doc
- Documentation related files.dev
- Development notes.
To keep things in order and avoid naming conflicts, C++ namespaces should
reflect above directory structure within include
and src
directories.
CuteHMI uses Qbs as build system, therefore each extension starts its life in dedicated Qbs file. Extensions tend to produce many by-products (like tests for example), so commonly they are defined within Project Qbs item.
Project item allows one to put inside a collection of products or reference subprojects. What products are placed inside project item depends on type of extension. CuteHMI provides customized Product items and a bunch of helper Qbs modules for fast and convenient extension setup.
Every CuteHMI product item requires one to provide some basic metadata, such as
vendor name, description or domain. This information can be extracted by
cutehmi.metadata
Qbs module and exported to JSON or header file.
Extensions can be divided into two major categories:
- pure QML extensions
- extensions that use C++ code
Pure QML extensions are easy to deal with. In their roots they only need
qmldir
file to work. Such extensions should be defined with
cutehmi.Extension
product item, which takes care about things like tagging or
intalling files to correct destination directories.
If you would like to provide a QML extension, then you should refer to
documentation on qmldir files. You can either create qmldir by hand or use
cutehmi.qmldir
Qbs module dependency to generate it automatically.
Depends { name: "cutehmi.qmldir" }
You can also use cutehmi.qmltyperegistrar
Qbs module dependency to generate
plugins.qmltypes
file (though it is rather needed by C++ QML plugins).
Extensions that use C++ code are more sophisticated than QML extensions. In
order to create C++ extension one should use cutehmi.CppExtension
product
item. It hides unpleasant C++ aspects, so extension creator just has to provide
the metadata and add some C++ or QML files (cutehmi.CppExtension
extends
cutehmi.Extension
). To boost the process of creating C++ extension even
further one may use cutehmi.skeleton.cpp
module by adding it as product
dependency.
Depends { name: "cutehmi.skeleton.cpp" }
This module will create skeletal header and source files commonly found in all C++ extensions. In fact it will create minimal operational C++ extension, which can be then provided with some custom code to do something useful.
C++ extensions can act as libraries (to be linked against), QML plugins or they can be a mix of both.
To create an extension that behaves like a library (i.e. other extensions can be
linked against it) you should refer to Qt documentation on
Creating Shared Libraries.
Note that mentioned MYSHAREDLIB_EXPORT
macros are defined within
platform.hpp
generated by cutehmi.skeleton.cpp
module, according to the
pattern VENDORNAME_EXTENSIONNAME_API
.
To make an extension that acts as a QML extension, but uses C++ code, you may
refer to Creating C++ Plugins for QML.
You can ignore the creation of .pro
files and simply add files to Qbs project.
Key concepts to focus on is a class that extends QQmlExtensionPlugin
,
qmlRegisterType
function template that exposes QObject
derived classes and
entries in qmldir
file. You may also check out the tutorial
Writing QML Extensions with C++.
C++ extensions, which act as QML plugins must provide QML plugin class. By
convention in CuteHMI this class is called QMLPlugin
and must be inside
extension's internal
namespace. Sticking to this convention allows one to
rely on defaults, when using other Qbs helper modules, such as cutehmi.qmldir
for example.
All CuteHMI naming conventions have been defined in cutehmi.conventions
Qbs
module, which can be referenced by other Qbs modules. Among them there is
mentioned QML plugin class name (qmlPluginClassName
property). All properties
can be printed to the console by setting debugOutput
property to true
. From
the command line this can be done as follows.
qbs resolve -f CuteHMI.qbs modules.cutehmi.conventions.debugOutput:true
To print naming conventions for specific extension replace modules
with
products.Vendor.Extension.MajorVersion.cutehmi.conventions.debugOutput:true
.
As stated - sticking to naming conventions is not hard requirement, but it helps avoid setting property values of various Qbs modules explicitly.
Following extensions can be used as a reference for creating custom extensions:
- Templates.QMLSkeleton - pure QML extension
- Templates.CppSkeleton - C++ extension
- Templates.CppPluginSkeleton - C++ extension with QML plugin
Often you may want to keep your extension in separate repository. A simple
trick, which makes life easier is to create .gitignore
file in extensions
directory and make Git ignore the file itself along with your extension(s).
/.gitignore
/YourExtension
This way you can clone your extension to extensions
subdirectory and your
reposiotry won't interfere with CuteHMI repository.