Text editing improvements in Qt Quick

In the effort to make Qt Quick and Qt Quick Controls a suitable replacement for Widgets in more applications, we identified the text editing use case as something that needed improvement, since many applications need it, and since a text editor is a common sort of demo application that tends to be rewritten in various ways over the years.

Access to the document object


An instance of TextEdit (or its subclass TextArea) uses a QTextDocument as the "model" to be rendered and interactively edited. Early in Qt 5, the textDocument property was added as a way to expose the internally-created QTextDocument instance to your application's C++ code; the first use case was to allow installing a QSyntaxHighlighter.

But users have been asking not only to access the internally-created instance, but to be able to replace it.  To make that possible, we had to get rid of the old private QQuickTextDocumentWithImageResources subclass and find another way to handle resource loading (such as inline images loaded over http). QTextDocument::loadResource() looks for a method with the signature QVariant loadResource(int, QUrl) in its parent object; accordingly, QQuickTextEdit::loadResource() is now implemented, and by default, TextEdit is the parent of its own document.

QQuickTextDocument::setTextDocument() is new in Qt 6.7.  If you replace the document object, remote resource loading becomes your responsibility, if you need it.  It can be done by the parent object's loadResource() function, by a resourceProvider(), or by overriding loadResource() in your own QTextDocument subclass.

collab.dot

Loading and saving

TextEdit.textDocument already existed, so the QQuickTextDocument object that it returns was an obvious place to add new QML-facing API.

The pattern for loading media files so far in QML is to have a source property that takes a URL.  We added this property to QQuickTextDocument, so you can set TextEdit.textDocument.source just as you can set Image.source.

Until now, QML has offered almost no API for writing files.  (One exception is that Item.grabToImage() returns an object that has a saveToFile() function.  It's useful for saving screenshots in your application.)  Perhaps in the future we should have a general file I/O API; but for now we don't have it, and nearly every text editor needs to be able to save files, ideally without having to write boilerplate C++ code each time.  So we added TextDocument.save() and saveAs() functions for now.

The new API has Tech Preview status, until we resolve whether or not to replace it with a more general-purpose file I/O API.  In a widget application, you need to get the raw contents of the QTextDocument from one of its accessors such as toHtml() or toMarkdown() and then write it with QFile; maybe the QML API could work that way too.  The pattern of using a source property for loading seems particularly succinct and declarative by comparison; but we are concerned whether the save()/saveAs() API is safe enough and flexible enough for everyone's use cases.  Feedback is welcome about this.

One thing that came up right away is if you need to extract data from text that is being loaded or modify what is being saved, for example to deal with YAML metadata in Markdown files, the TextDocument QML API does not directly accommodate that.  We are adding QTextDocument::metaInformation(FrontMatter) in 6.8.  (Parsing the front matter is orthogonal; for example you could use yaml-cpp.  There are also other formats in use besides YAML.)

File conversion is possible too.  The existing textFormat property now allows you to convert between formats, and also to toggle between WYSIWYG and raw markup.

Text formatting API

In C++, one often needs a QTextCursor to define a range of text to modify and apply styling to it.  The analogous QML type is TextSelection, which (so far) provides alignment, color, font and text properties. TextEdit.cursorSelection corresponds to the text that the user has selected, so you can have bindings to controls to show and change block and character format properties.  See the Text Editor example for details.

Large documents

As pointed out on a What's New page a while back: in the last few Qt versions, Text and TextEdit avoid generating scene graph nodes for large portions of text that fall outside the viewport.  This is in fact based on a general mechanism for an Item to act as the viewport for (some of) its children, introduced here: the parent (such as Flickable) sets its ItemIsViewport flag, and the child (such as TextEdit) sets its ItemObservesViewport flag.  The child's clipRect() provides the region that is visible in the viewport. TextEdit then populates scenegraph nodes only for text blocks that overlap the clipRect, if the text is considered "large" enough to need this optimization.  The tradeoff is that its updatePaintNode() is called more often during scrolling, and each time it needs to figure out which blocks should be visible in the viewport.

In the screen recording below, the inner rectangle shows the extents of the viewport, and you can see how paragraphs disappear when they are scrolled outside that region.  The Markdown file contains the entire text of a book which is known for its length; and yet the memory usage is more reasonable than it was in older versions of Qt that populated scene graph nodes for all blocks at once:large-textedit-scrolling

Going forward

In summary, you can write a WYSIWYG rich text editor in pure QML now.  The Text Editor example that we ship no longer needs C++ beyond a basic main() function.


Blog Topics:

Comments

Commenting for this post has ended.

Jakub Narolewski
3 points
55 months ago

Awesome :] Any news about potential changes in QWebSockets module? :P

Mårten Nordheim
0 points
55 months ago

Hey, thanks! For 6.0 it was decided that we want to have a more limited scope to focus on the core/base components, so QWebSockets is not 'officially' supported in 6.0. But it will be brought back shortly after! I don't know anything more about the plans beyond that I'm afraid.

C
cliff
2 points
55 months ago

Could you remove the limit of 6 instances of QNetworkAccessManager? And I can't understand why you do this limit for Qt5?

Mårten Nordheim
0 points
55 months ago

There's no limit on how many instances of QNetworkAccessManager you can have (although our documentation recommends only having one instance). Are you perhaps talking about the limit of parallel connections QNetworkAccessManager will make in the background to a single host? That limit only counts for HTTP 1(.1) and was/is fairly standard. For HTTP/2 it's actually limited to only 1 connection, but thanks to how the protocol is made up it can actually send multiple requests per connection, so it doesn't need any more.

C
cliff
0 points
55 months ago

Yes, I am talking about the parallel connections. But I still hope you to remove this limit because QNetworkAccessManager is a very useful library et everywhere needs it, even in some urgent cases. For example, in the Security Industry with alarms, we hope continuously post the images immediately. If you still won't change ideas, it doesn't matter. But if we buy a commercial license, could you help us to remove this limit?? Thanks.

PS, the Http protocol is the best way to communicate with other languages et system, doesn't it? So I hope you can seriously think about it. Thanks again.

Mårten Nordheim
0 points
55 months ago

If you compile Qt on your own then as far as I'm aware (never tried) this is the only line that needs to be changed to increase it: https://github.com/qt/qtbase/blob/dev/src/network/access/qhttpnetworkconnection.cpp#L68

C
cliff
0 points
55 months ago

Thanks, I will try immediately. But I want to know what's your philosophy to set the value 6 ? I know a book named "the philosophy of Unix" which explains many things at the top level. And I want to get the same philosophy from you.

Mårten Nordheim
0 points
55 months ago

I wasn't around when the choice was made so I don't know the definitive source, but it was chosen to be 6 since that matched what major browsers used at the time (and largely still do for HTTP 1) and was considered a reasonable fit for Qt as well.

https://stackoverflow.com/a/30064610/2493610

C
cliff
0 points
55 months ago

You are so kind and I now know everything about it. In fact, the browsers are the applications for the client, so they can set the value 6 because peoples havn't 20 or 1000 eyes (another reason is the work of rendering is heavy). But Qt is a neutral library, the developers should do what they want, only limited by OS or physical. So removing the limit is still a good idea, this is only my suggestion, you can do as you like.

Last question, if I modify it myself, set defaultHttpChannelCount = 1000 even 60000, that's enough? Should I modify the other things, by example, which are the best values for defaultPipelineLength and defaultRePipelineLength when HttpChannelCount is 1000 or 60000? Sorry I have troubled you so much. But this is really an interesting thing.

Mårten Nordheim
0 points
55 months ago

Right, as I mentioned earlier I haven't tried it myself, but in theory changing defaultHttpChannelCount is enough. Pipelining isn't enabled by default so unless you enable it it's not going to make a difference what you set those to. Do keep in mind that if you set it to 60000 then it will allocate 60000 channels as well, even if for some connections you only end up using 10. There might also be limits on the server on how many connections you, or perhaps anyone in total, can establish before they start getting dropped.

Markus Goetz
0 points
49 months ago

I recommend to vote and subscribe to and maybe comment on https://bugreports.qt.io/browse/QTBUG-25280 :-)

ekke
2 points
55 months ago

good to see that there's some work on QTBUG-86966 to provide infos on Network Connection State. really need this for nearly all of my apps.

Leonid Dulman
0 points
55 months ago

Hi to all What I can use insteed of QNetworkConfiguration Thank you

Mårten Nordheim
0 points
55 months ago

QNetworkConfiguration has a few things available. There's no replacement for it in 6.0 though. What about QNC are you currently using?

Carlos Enrique Pérez Sánchez
0 points
55 months ago

Can be the linking between Qt Network and QML optional? I don't see why should I deploy Qt Network in an application that does not uses network capabilities at all.

Ed
0 points
46 months ago

Redirect not happening automatically and I can't seem to get it to work with any policy.

G
GamesForOne.com
0 points
45 months ago

Where is the FTP plugin in Qt 6.1.2?

G
GamesForOne.com
0 points
43 months ago

Still no FTP plugin or support in Qt 6.2?