📜 ⬆️ ⬇️

Hybrid applications in Qt using D3.js as an example

D3 is a powerful JavaScript library for visualizing data. In my opinion, this is just a paradise for a web developer, seemingly inaccessible to a Qt programmer. But the flexibility of the Qt framework allows you to integrate a web-frontend into a thick client using the Qt Web Bridge mechanism. Such applications are called hybrid ( Qt Hybrid Apps ).

For JavaScript programmers, the good news is that their solutions can be easily integrated into Desktop applications, which could potentially increase the target audience of users of the libraries being developed (in any case, this is true for the world of Qt applications).

The screenshot below shows the Dependency Wheel widget (Circle of Dependencies), which is drawn with the help of D3.js and the data and display is managed with the help of Qt. When a pointer is located above the corresponding arc, its interrelationships are “highlighted”, and the rest become semi-significant. This widget can be used to visualize various kinds of dependencies (for example, libraries).
')
Unlike the original JS solution, the diagram dynamically changes the size to fit the widget, and the data is set on the Qt side, rather than by loading a JSON file.

The article is more focused on Qt-programmers, but it can also be interesting for JS programmers.



The idea of ​​hybrid applications


The starting point of the idea of ​​hybrid applications is a number of limitations inherent in native applications:

Hybrid applications solve these problems due to the fact that:

The hybrid application architecture suggests that

Thus, hybrid applications implement the idea of ​​a thin client.
One example of hybrid applications in Qt is WebKit Image Analyzer .

In the example considered in the article, only part of the hybrid application approach will be used: component mapping due to JavaScript. In this case, all the necessary JS files will be located in the resources, as in the classic StandAlone application (standalone and does not require an intranet / Internet connection to work).

Project structure


The general structure of the project files is shown in the figure:



The base directory contains:

In the charts / pie directory:

The resources directory is divided into two: js and html. The html contains the page that will be loaded in the widget and contains all the Qt interaction code, js contains the js files necessary for the DependencyWheel to work: common for D3 is d3.min.js and example-specific is d3.dependencyWheel. js.

Class diagram


In order to reduce the volume of the article using VisualParadigm, a simple diagram was created: it omits the attributes and methods of classes that are not directly related to the described technology. Details of the implementation can be found in the source code, link to which is located at the end of the article.



Qt <-> JS Interaction


In hybrid applications, a special object is embedded in JavaScript, the method call of which is processed on the Qt side:

void D3Viewer::addContextObject(const QString &name, QObject *object) { frame()->addToJavaScriptWindowObject( name, object ); //frame() - QWebFrame } 

This method is called in the D3Viewer-derived classes in the constructor before loading the page:

 addContextObject("api", this); 

Further, the interaction of Qt with JS is possible through four mechanisms:
  1. By referring to the properties of the object.
    for this you need to define a property in the object, which is a context object in JS ("api"):

     public: Q_PROPERTY(float padding READ padding WRITE setPadding) public slots: float padding(); //getter void setPadding(const float padding); //setter 

    After that, you can access these properties from JS:

      var chart = d3.chart.dependencyWheel() .width(api.width) .height(api.height) .margin(api.margin) .padding(api.padding); 

  2. Signal processing Qt in JS, for this in JS, you must connect the appropriate handler function to the signal.

     api.update.connect(redraw); 

  3. Calling Qt slots in JS, for example, when processing a click on an element:

      g.append("svg:path") .style("fill", fill) .style("stroke", fill) .attr("d", arc) .on("mouseover", fade(0.1)) .on("mouseout", fade(1)) .on('click', function (d) { api.itemClicked(packageNames[d.index]) } ); //   

  4. By calling other Qt methods in JS, for this method declaration must be preceded by the Q_INVOKABLE macro.

     Q_INVOKABLE void thisMethodIsInvokableInJavaScript(); 

  5. Direct execution of the JS code.

     void D3Viewer::evaluateScript(const QString &script) { frame()->evaluateJavaScript(script); } 

In the example, methods 4 and 5 are not used.

Debugging JavaScript in a hybrid application


To debug a JS application (like a DOM overview, view network activity, downloadable resources, etc.), you need to set the following property in the D3Viewer designer:

 #ifdef QT_DEBUG //           page()->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); #endif 

Then at run time, the context menu item “Inspect” will be available in the context menu (right-click on QWebView).



Having chosen which window of Web-inspector is displayed.



In this window, on the Scripts tab, you can enable debugging.



Setting a breakpoint is done by clicking on the corresponding line number on the left.
PS In Qt 4.8.6 I never managed to intercept breakpoint. In 5.3.0 everything is working properly.

disadvantages


Any solution has both advantages and disadvantages. And in this case, D3.js will have to pay its price for the “prettiness”.

Source


The source code of the sample is available here .
The example was built and run under Qt 4.8.6 and 5.3.0.

Source: https://habr.com/ru/post/218389/


All Articles