Plugin QML Architecture

Hey all,

Can anyone give me a hand with getting custom QML to load with the plugin architecture? I’ve made a simple test QML, and have “loaded” it with the following code:

const QVariantList& FugroFirmwarePlugin::toolBarIndicators(const Vehicle* vehicle) {
    Q_UNUSED(vehicle);
    if (_toolBarIndicatorList.size() == 0) {
        _toolBarIndicatorList.append(QVariant::fromValue(QUrl::fromUserInput("qrc:/custom/ZoomControl.qml")));
    }

    return _toolBarIndicatorList;
}

In the FirmwarePlugin override - however I haven’t seen any changes to any part of the QGC UI.

I have the rest of the plugin framework down, have my own code for various systems running, but I can’t find any information on how to add my own custom QML.

Anyone able to help? I shouldn’t need much - just an understanding of how to get some QML loaded. Once I’ve seen it done, I shouldn’t have any trouble replicating/expanding in future.

Thanks!

That should work. But keep in mind it will replace the entire toolbar with your single thing since you aren’t calling the base class first to fill in the list. I’m not sure that is what you want.

You are going to need to debug it to figure out what is going on. Look for debug output which may give you some hints about something wrong in control Determine if your control is even created. You can do that with something like Component.onCompleted: console.log("control created" in your control. If it’s created but not shown then it’s likely a visibility of sizing issue. Looks like you are using Stable which is a bit of a pain to get toolbars to show up correctly. Make sure you are following the example of other implementations. Master has a lot of fixes to make this work more easily. If you control is not created at all, then debug from the Repeater Qml in the toolbar code to figure out why the model for the Repeater is incorrect.

Thanks @DonLakeFlyer, I’ll keep all of that in mind as I continue experimenting!

It looks like the function itself toolBarIndicators() isn’t actually being called - the constructor of the Firmware Plugin is being called (through a Firmware Plugin Factory) but toolBarIndicators() doesn’t appear to be. Similarly, my overrides for adjustIncomingMavlinkMessage() and adjustOutgoingMavlinkMessage() also don’t appear to ever fire, even with a vehicle attached.

Would there be any obvious reasons you can think of as to why this might happen? I’ll keep experimenting to see if I can figure out the cause, but any pointers you could give would be greatly appreciated!

I have now tested the example plugin - everything seems to be working there. My custom plugin however, though appearing to be setup the same way, still won’t load the QML

I’ve placed onCompleted calls in the component, with nothing being output. I am receiving onCompleted output from all parts of the Repeater QML in MainToolBarIndicators.qml, so that appears to be functioning as intended

My QML is as follows - have I made some small dumb mistake there? It’s listed in the .qrc, and the import path used in the FirmwarePlugin matches the path I am given by the .qrc when I right-click.

import QtQuick 2.11

Item {
    Component.onCompleted: console.log("ZoomControl Item Complete")

    Rectangle {
        width: 200
        height: 100
        color: "red"

        Component.onCompleted: console.log("ZoomControl Rectangle Complete")

        Text {
            anchors.centerIn: parent
            text: "Example Text"

            Component.onCompleted: console.log("ZoomControl Text Complete")
        }
    }
}

Last, is there another area in the Plugins where I can load up QML, if the Toolbar section is buggy? Off the top of my head, the only other place I can recall is when loading Settings pages in the CorePlugin override. I haven’t yet located the area to load QML for flight-mode overlays etc

Thanks for your help in advance [anyone], I appreciate the time taken!

Solution found! I’ll pop the details here in case anyone missed the same thing I did.

I’ve been building this plugin from scratch based off what I’ve gleaned from examining and running the example plugin. I’m also very new to Qt and QML in general, though I have a great deal of C++ and general programming/application design experience.

For the QGroundControl plugin, the “starting point” for inserting your own QML is the CustomOptions class which overrides QGCOptions. This class can be found in CustomPlugin.h/.cc in the example plugin.

CustomOptions defines a series of in-line function overrides - the ones that return the QUrl type are used to place QML into QGroundControl:

class CustomOptions : public QGCOptions
{
public:
    CustomOptions(CustomPlugin*, QObject* parent = nullptr);
    bool        wifiReliableForCalibration      () const final { return true; }
#if defined(Q_OS_LINUX)
    double      toolbarHeightMultiplier         () final { return 1.25; }
#endif
    QUrl        flyViewOverlay                  () const final { return QUrl::fromUserInput("qrc:/custom/CustomFlyView.qml"); }
    QUrl        preFlightChecklistUrl           () const final { return QUrl::fromUserInput("qrc:/custom/PreFlightCheckList.qml"); }
    //-- We have our own toolbar
    QUrl        mainToolbarUrl                  () const final { return QUrl::fromUserInput("qrc:/custom/CustomMainToolBar.qml"); }
    QUrl        planToolbarUrl                  () const final { return QUrl::fromUserInput("qrc:/custom/CustomMainToolBar.qml"); }
    //-- Don't show instrument widget
    CustomInstrumentWidget* instrumentWidget    () final { return nullptr; }
    bool        showMavlinkLogOptions           () const final { return false; }

    bool        showFirmwareUpgrade             () const final;
    //-- We handle multiple vehicles in a custom way
    bool        enableMultiVehicleList          () const final { return false; }
    //-- We handle our own map scale
    bool        enableMapScale                  () const final { return false; }
    // TODO: Can't access QGCPalette without some workarounds, change this upstream
    QColor      toolbarBackgroundLight          () const final;
    QColor      toolbarBackgroundDark           () const final;
};

For example the mainToolbarUrl override allows you to define what QML file to load in place of the default mainToolbarUrl defined by base QGC. Replace the resource path here to point to your new QML. After this, all you need to do is define the CustomOptions constructor (which is empty, it just calls the base constructor):

CustomOptions::CustomOptions(CustomPlugin*, QObject* parent) : QGCOptions(parent)
{
}

Then create an instance of the class in the CustomPlugin constructor (_pOptions here is a member variable, so add that to your CustomPlugin class):

CustomPlugin::CustomPlugin(QGCApplication *app, QGCToolbox* toolbox) : QGCCorePlugin(app, toolbox)
{
    _pOptions = new CustomOptions(this, this);
    _showAdvancedUI = true;
}

Then finally declare and define the options override. In CustomPlugin.h:

QGCOptions* options () final;

And in CustomPlugin.cc:

QGCOptions* CustomPlugin::options()
{
    return _pOptions;
}

Once you’ve done these base steps, your custom QML toolbar should load into QGC instead of the base toolbar. To show it functioning, the following image is my extremely high-tech example from my previous comment:

Thanks @DonLakeFlyer for your help, it would have taken me much longer to puzzle this out without your comments!

I’ll add that I didn’t have the CustomFirmwarePluginFactory set up correctly - it was using MAV_AUTOPILOT_PX4 instead of MAV_AUTOPILOT_ARDUPILOTMEGA which was required for use with an Ardusub vehicle. As a result, none of my CustomFirmwarePlugin functions were being called.

All functions are now being called as required, and ::toolBarIndicators can now insert QML directly into the toolbar without requiring the process described in my post above