Code on demand with REST

Code on demand

In the paper Architectural Styles and the Design of Network-based Software Architectures by Roy Thomas Fielding, REST is defined. Today, when people think of REST, they often associate it with CRUD operations, more or less. However, one of the properties defined in the paper is ‘Code on Demand,’ where the client can request a script or binary from the server (similar to what happens when a browser requests JavaScript files to extend the behavior of a web page).

It’s nice, but it’s not exclusive to the web world. Take a look at this example in Qt:

import QtQuick
import QtQuick.Controls 2.5

Page {
    id: page
    Button {
        text: "load"
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        onClicked: {
            loader.source = new URL("https://server/test.qml")
        }
    }

    Loader {
        id: loader
        anchors.verticalCenter: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
    }
}

When you click the button, we load:

import QtQuick
import QtQuick.Controls 2.5

Item {
    anchors.fill: parent

    Column {
        anchors.fill: parent
        TextField {
            width: dp(100)
            placeholderText: "Write here"
        }
        Button {
            text: "Ok"
            onClicked: {
            }
        }
    }
}

I think that the power of code on demand is sometimes underrated, yet we have the tools to leverage it. Please try to use it in a real case.

Barcode, OpenCV and QML (Qt 6)

In this post, I would like to share how to build a barcode scanner (for Android and iOS) in QML using OpenCV.

As a first step, we need to build the OpenCV library or download it from here: https://github.com/niqt/ocvdecoder (the readme discusses QR code scanning, but the library can also be used for barcode scanning).

To reach the goal we have to do:

  • Create the C++ class
  • Create the QML Item
  • Use the QML component

C++

The logic involves receiving frames from the camera and decoding them until the barcode is recognized. These frames are captured by videoSync, which is the object responsible for obtaining frames from the camera.

/*
 * This class implement a qml wrapper for the OpenCV bar code feature
 * */

class Bardecoder : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QObject* videoSink WRITE setVideoSink) // the video sync used to get the frames
    Q_PROPERTY(bool run READ run WRITE setRun) // start/top the decode
public:
    Bardecoder(QObject *parent = nullptr);
    void setVideoSink(QObject *videoSink);
    bool isDecoding() {return m_decoding; } // the status of the decoding - decondig in progress
    void setRun(bool run);
    bool run() {return m_run;}
public slots:
    void setFrame(const QVideoFrame &frame);


signals:
    void videoSyncChanged();
    void decoded(const QString &code);

private:
    QVideoSink *m_videoSink;
    cv::Ptr<cv::barcode::BarcodeDetector> m_bardet;
    QFuture<void> m_processThread;
    bool m_decoding;
    bool m_run;
};

The Bardecoder class is a QML_ELEMENT. It has two properties: videoSync (to set the videoSync) and run to initiate the decoding process.

The setFrame slot is invoked when videoSync captures a frame from the camera.

In the private section of the class, the key components are m_barded (the OpenCV barcode decoder) and m_processThread (the thread that executes the decoding action).

In the cpp part we have:

Bardecoder::Bardecoder(QObject *parent)
    : QObject{parent}
{
    m_bardet = makePtr<barcode::BarcodeDetector>("", ""); // create the bardetector
}

void Bardecoder::setVideoSink(QObject *videoSink)
{
    m_videoSink = qobject_cast<QVideoSink*>(videoSink);

    if (m_videoSink) {
        QObject::connect(m_videoSink, &QVideoSink::videoFrameChanged, // when a frame is received call setFrame
                         this, &Bardecoder::setFrame);
    }
}

void Bardecoder::setFrame(const QVideoFrame &frame)
{
    if(m_run && !isDecoding() && m_processThread.isFinished()) { // if start decode and is not decoding and the previous is finished => decode the frame
        m_decoding = true;
        QImage image = frame.toImage().convertToFormat(QImage::Format_RGB32).rgbSwapped(); // convert the frame to an imageso can be used from opecv

        m_processThread = QtConcurrent::run([=]() {
            if(image.isNull()) {
                m_decoding = false;
                return;
            }
            cv::Mat img(image.height(), image.width(), CV_8UC4, (void *) image.bits(), image.bytesPerLine());
            vector<Mat> points;

            vector<Point> corners;
            vector<cv::String> decode_info;
            vector<string> decode_type;
            m_bardet->detectMulti(img, corners);
            m_bardet->detectAndDecodeWithType(img, decode_info, decode_type, corners);
            if (decode_info.size() > 0) {
                auto code = QString(decode_info.at(0).c_str());
                if (code.length() > 0) // if the barcode is decoded emit the signal
                    emit decoded(QString(decode_info.at(0).c_str()));
            }
            m_decoding = false;
        });
    }
}

void Bardecoder::setRun(bool run)
{
    m_run = run;
}

The two main components are setVideoSync, which sets the slot, and setFrame. When a frame changes, setVideoSync triggers setFrame.

Within setFrame, the decoding action occurs. This process begins if run is true (where run functions as a start command). The decoding action starts only if there is no other frame currently being decoded and if the previous decoding thread has finished. Before using the OpenCV decoder functions, it’s necessary to transform the frame into an image in a specific format. Once this transformation is complete, the OpenCV functions are called as detailed in the OpenCV documentation. If the barcode is successfully decoded, a signal is emitted.

QML

Create the BarReader QML component:

import QtQuick
import Felgo
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.1
import QtMultimedia
import bardecoder

/*
  This element contains the logic and the components for the Barcode reader.
  It use the camera, the CaputureSession to capture the video from the camera and it sent
  to VideoOutput to be shown. The decoder get the video frames using the videoSync from the VideoOutput.
  The signal barCaptured is emitted if the decoder found a barcode.
  */

Item {

    id: item

    // This property enable/disable the decoding action
    property alias run: decoder.run

    // This signal is emitted when the barcode is found
    signal barCaptured(string barcode)

    Camera // The camera
    {
        id:camera
        active: run
        focusMode: Camera.FocusModeAuto
    }

    CaptureSession {
        camera: camera
        videoOutput: videoOutput
    }

    VideoOutput
    {
        id: videoOutput
        anchors.fill: parent

        // The follow properties are used to scale the red capture area on the camera video
        property double captureRectStartFactorX: 0.25
        property double captureRectStartFactorY: 0.25
        property double captureRectFactorWidth: 0.5
        property double captureRectFactorHeight: 0.5

        MouseArea { // Clicking remove the autofocus
            anchors.fill: parent
            onClicked: {
                camera.customFocusPoint = Qt.point(mouseX / width,  mouseY / height);
                camera.focusMode = Camera.FocusModeManual;
            }
        }

        Rectangle { // The capture zone
            id: captureZone
            color: "red"
            opacity: 0.2
            width: parent.width * parent.captureRectFactorWidth
            height: parent.height * parent.captureRectFactorHeight
            x: parent.width * parent.captureRectStartFactorX
            y: parent.height * parent.captureRectStartFactorY

        }
    }

    Bardecoder { // The barcode decoder
        id: decoder
        videoSink: videoOutput.videoSink // in this way capture the video frames

        onDecoded: code => {
                       console.log("QR " + code)
                       run = false // stop to decode
                       barCaptured(code) // send the value
                   }
    }
}

The logic behind this process is detailed in the code comments. The only aspect not specified in these comments is that we use a pink (red with opacity)rectangle to define the area of the camera used for decoding.

Now, we can integrate it into our page in the following way:

BarReader {
        id: isbnReader
        visible: false
        run: false
        anchors.fill: parent
        onBarCaptured: barcode => {
            visible = false // hide itself
            run = false // stop to scanning
        }

    }

A working example of this project can be found here: https://github.com/niqt/felgobar. Although it uses Felgo, the same principles apply to pure Qt as well.

TableView in QML

Take a look at how the TableView works in Qt6 with QML. If we refer to the example from the official documentation, we find this code:

 import QtQuick 2.14
 import Qt.labs.qmlmodels 1.0

 TableView {
     anchors.fill: parent
     columnSpacing: 1
     rowSpacing: 1
     clip: true

     model: TableModel {
         TableModelColumn { display: "name" }
         TableModelColumn { display: "color" }

         rows: [
             {
                 "name": "cat",
                 "color": "black"
             },
             {
                 "name": "dog",
                 "color": "brown"
             },
             {
                 "name": "bird",
                 "color": "white"
             }
         ]
     }

     delegate: Rectangle {
         implicitWidth: 100
         implicitHeight: 50
         border.width: 1

         Text {
             text: display
             anchors.centerIn: parent
         }
     }
 }

It produces this:

If we add more columns to the model in the following manner:

model: TableModel {
                    TableModelColumn { display: "name" }
                    TableModelColumn { display: "color" }
                    TableModelColumn { display: "feet" }
                    TableModelColumn { display: "fly" }

                    rows: [
                        {
                            "name": "cat",
                            "color": "black",
                            "feet": "four",
                            "fly": "no"
                        },
                        {
                            "name": "dog",
                            "color": "brown",
                            "feet": "four",
                            "fly": "no"
                        },
                        {
                            "name": "bird",
                            "color": "white",
                            "feet": "two",
                            "fly": "yes"
                        }
                    ]
                }

we have:

In this approach, not all elements are visible because each cell is of the same size. While this uniformity can be visually appealing, it may not always be practical. Next, let’s see how we can add a custom header and adjust the cells to have widths based on the size of the elements within them.

Let’s start with the header:

           HorizontalHeaderView {
                id: horizontalHeader
                anchors.left: tableView.left
                anchors.top: parent.top
                syncView: tableView
                clip: true
                model: ["Name", "Color", "Feet", "Fly"]
                delegate:
                    Rectangle {
                        implicitWidth: txt.width + dp(5)
                        implicitHeight: txt.height + dp(5)
                        color: "darkgray"
                        AppText {
                            id: txt
                            text: modelData
                            anchors.centerIn: parent
                            color: "white"
                        }

                }

            }

First, we attach the header to the left side of the table and set the syncView to the tableView. This ensures synchronization between the table and the header, meaning each header column aligns with its corresponding column in the tableView. Next, we define the labels for the header. Finally, we see in the delegate how to display the header’s cells.

The tableView:

            TableView {
                id: tableView
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: horizontalHeader.bottom
                anchors.bottom: parent.bottom
                columnSpacing: 1
                rowSpacing: 1
                clip: true
                contentWidth: parent.width
                columnWidthProvider: column => { return -1; }
                model: // is the same

                delegate: Rectangle {
                    implicitWidth: Math.max(horizontalHeader.columnWidth(column), txtRow.width) + dp(5)
                    implicitHeight: txtRow.height + dp(10)
                    border.color: "darkgray"
                    Text {
                        id: txtRow
                        text: display
                        anchors.centerIn: parent
                    }
                }
            }

The model remains the same as previously mentioned and is not included here. Regarding the positioning, the top of the table is anchored to the bottom of the header, as one would expect. The code enabling cell width to be based on the element’s width within the cell is as follows: columnWidthProvider: column => { return -1; }. The columnWidthProvider property expects a function that returns the width for a column. However, if it returns -1, the size is automatically calculated based on the size of the element in the cell.

Another important aspect to note is the use of the following code:

implicitWidth: Math.max(horizontalHeader.columnWidth(column), txtRow.width) + dp(5)

Why is this necessary? It ensures that if the text in the header of a column is wider than the text in the data cells, the size of the header remains readable. This code calculates the maximum width between the header and the row text, and then adds a small margin (dp(5)) for better visual spacing.

The final result is:

“Note: English is not my native language, so please excuse any errors. I would appreciate your assistance in correcting them.”

Close Keyboard on IOS

In the last week-end, while I was working on my side project, I discovered a problem: on iOS the keyboard stay opened after it’s opened due to a click in an input text. The obvious solution that I thought is:

Page {
      MouseArea {
                anchors.fill: parent
                enabled: Qt.inputMethod.visible
                onClicked: Qt.inputMethod.hide()
      }
      //.... input fields
}

Thus in this way, clicking/tapping on the screen, the keyboard should disappear. In my case it doesn’t work because the input fields are in a ScrollView (and if you move the MouseArea in the ScrollView the view doesn’t scroll).

My solution:

Page {
      MouseArea {
                anchors.fill: parent
                enabled: Qt.inputMethod.visible
                onClicked: Qt.inputMethod.hide()
                propagateComposedEvents: true
                z: 10
      }
      ScrollView {
        id: scrollView
        anchors.fill: parent
        anchors.bottomMargin: dp(60)
        clip: true
        contentWidth: -1

        ColumnLayout {
            id: colView
            //.... input fields
}

The tips&trick is set a Z value to the MouseArea and propagates the mouse event to the object in the bottom layers.

Qml – Copy And Paste

Sometimes in mobile applications is helpful can copy text between applications (i.e. wifi password or simply text). With a simple Text is impossible to do the copy action; it is possible using the QClipBoard, so we have to use a bit of C++.

In my example, we see how to copy the text from a ListItem and past where we want. Start looking at the Qml code:

App {
    NavigationStack {

        Page {
            title: qsTr("Main Page")

            AppToolTip {
                 id: copyTip
                 text: "Copied"
            }

            AppListView {
                id: listView
                anchors.fill: parent

                delegate: SimpleRow {
                    item: modelData
                    showDisclosure: false
                    onSelected: {
                        copyTip.target = listView.currentItem
                        clipboard.setText(detailText)
                        copyTip.open()
                    }
                }

                model: [
                    { text: "WIFI ID",
                        detailText: "MyWifi" },
                    { text: "WIFI PASSWORD",
                        detailText: "1234562"}
                ]
            }
        }
    }
}

First, in the example I used https://www.felgo.com but you can use also simple Qml. In the code, we define a ListView with a simple model that has two fields: text and detailText. The delegate is SimpleRow that show these two pieces of information (you can use your custom delegate if you prefer).

In the code you can find an AppTooltip that we use to show a “Copied” message after we copied the text.

How do we copy the text? Simple we call the function setText of the clipboard object.

What’s a clipboard?

#ifndef MOBILECLIPBOARD_H
#define MOBILECLIPBOARD_H

#include <QObject>

class MobileClipBoard : public QObject
{
    Q_OBJECT
public:
    explicit MobileClipBoard(QObject *parent = nullptr);
    Q_INVOKABLE void setText(const QString &from);
    Q_INVOKABLE QString text();
};

#endif // MOBILECLIPBOARD_H

In the class MobileClipBoard we have two methods invocable from the Qml side. Let’s see what those methods do:

#include <QClipboard>
#include <QApplication>
#include "mobileclipboard.h"

MobileClipBoard::MobileClipBoard(QObject *parent) : QObject(parent)
{

}

void MobileClipBoard::setText(const QString &from)
{
    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(from);
}

QString MobileClipBoard::text()
{
    QClipboard *clipboard = QApplication::clipboard();
    return clipboard->text();
}

In the setText get the reference to the system clipboard and set the text in the clipboard.

The text() method simple return the text within the system clipboard.

The last point, bind the C++ world with the Qml side:

 MobileClipBoard clipboard;

felgo.qmlEngine()->rootContext()->setContextProperty("clipboard", &clipboard);

We declare a MobileClipboard and pass it to the root QQmlContext, so it can be used in the Qml side.

You can find the code here https://github.com/niqt/mobileclipboard

Note: English is not my native language, so I’m sorry for some errors. I appreciate it if your correct me.