HTML Text Editor - final solution

published at 23.02.2017 23:51 by Jens Weller

In the last post about my HTML Text Editor, I mentioned that while the editor worked like it should, other things didn't. I was able to fix at least some of the driver related issues, but kept seeing random crashes. So I decided to try out a different solution, instead of going on a long and tiring debugging trip.

This started with noticing, that there was a webchannel DLL in the Qt MinGW bin folder for 5.7. That made me realize, that QWebChannel does not depend on QWebEngine. QWebEngine depends on Chrome, and hence on windows only works with MSVC. Through the debugger, I did already know that this isn't a small embedded web browser in your application, it is actually quite a few threads:

../../files/blog/webenginethreads.png

Without the editor open, the program had 9 running threads, none of the named ones, see this tweet for details. While I'm not totally sure, it seems QWebEngine is running on 20+ threads.

My new solution does avoid this, as there is already a browser running in the background...

... so the final solution was, to open the editor through a link in the browser, and then use QWebChannel to connect between the JS running in the browser and the Qt Application. This solution makes a few things obsolete, as I don't need to replace any dialogs, like for links or images. On the JS side, only a plugin is needed to send the HTML to the Qt Application. Links and Images are still controlled by the Qt Application, which now has to write these into .js files, as this is the standard for TinyMCE3. The editor does not load in the application, and only when the link is clicked, a QWebSocketServer needs to be spawned. This code handles the link and installs a handler for the click:

void HTMLTextEditor::setUrl(const QString [&]url)

{

    QString html = R"(<'h2><'a href="%1">Open Editor)";// ' inserted to prevent this HTML to be turned into a link in the blog...

    QVBoxLayout* layout = new QVBoxLayout(this);

    QLabel* lbl = new QLabel(html.arg(url),this);

    layout->addWidget(lbl);

    QTimer* timer = new QTimer(this);
timer->setSingleShot(true); timer->connect(timer,QTimer::timeout,[this](){endpoint->setContent();}); lbl->connect(lbl,[&]QLabel::linkActivated,[this,timer](const QString[&] link){
startWebSocketServer();
QDesktopServices::openUrl(QUrl(link));
timer->start(1000);}); }

This method is a mock up, as in the other editors, this would be provided by QWebView. But as this isn't the case anymore, this code now needs to provide everything needed to open the editor in the browser. The Label with the link, and a timer. A handler is installed for when the link is clicked, first, the webSocketServer needs to be started, which also initializes the WebChannel object. Then, the URL is opened. The timer is installed, to fill in the content loaded from the model, once the editor is loaded inside the browser.

Which is one of the major differences. QWebChannel can't execute javascript, it only allows you to connect signal and slots to JS. But, JavaScript can execute JavaScript with eval, so the solution to this is, to have a signal execJS(const QString[&]), which on the JS side is giving the string to eval. This is only needed to set the content of the editor, as everything else is now running inside the browser.

Issues

The only real issue was, that tinymce isn't setup to run from file:// cleanly, as the Same Origin Policy prevents this a little. This showed in the actual JS Dialogs being empty, while the rest of the editor loaded ok. Once the html file with the editor was in the same folder with the JS, things worked though. One learned lesson from this was though, that when your triggered C++ code opens a dialog or does something else, it won't popup in front directly. Windows prevents this. There are some hacks around this, like calling setWindowFlags(Qt::WindowStaysOnTopHint);. Yet this doesn't feel natural.

And of course lots of refactoring, as now things are running a bit different as originally planned. The part for loading and saving is now handed to std::function calls, so that the client has to take care of this, the editor just uses these callbacks. And, every editor needs to connect to a different QWebSocket Server, as each editor has its own webchannel. This could be solved by writing some indirection layer, yet I don't expect the user to open to many such editors in parallel.

Also, this project, writing my own CMS started with the idea of using tinymce in combination with Qt. The integration is now a little different then I had planned in 2015. Already last year the HTML output with boostache was working, so the next weeks will be the final work on all the small features and fixes needed to get this running smoothly. The new website should be up in April.