Qt/QML: Text with inline QML elements - c++

We are building a graphical user interface with QtQuick/QML. We have some dynamic, multi-line text coming from a database, which should be displayed in the application. Currently, we are using the Text element to display the text. However, we need some QML components inline embedded into the text. For this, the text coming from the database contains placeholders such as ::checkbox|1:: which should then be replaced and displayed by the program.
In HTML, this is easy, you can simply mix inline elements with text to produce a result like this:
but in QML, this seems to be more difficult, as Text elements cannot be word-wrapped into two halves if there is not enough space (both the text and the container size should be dynamic).
The best solution we could come up with, is creating a Flow layout with one Text element for each word, but this seems too hacky.
Using RichText with HTML is not enogh, since we really need our custom QML elements in the text.
Also, we want to avoid using a WebView due to performance reasons.
Is there a sophisticated way to implement this with QML/C++ only?

You can create custom widgets and embed them into QML:
Writing QML Extensions with C++

I haven't tried placing something in the middle, but I did try adding a tag to the beginning (and I might try adding a tag at the end).
QML's Text has a lineLaidOut signal that let's you indent the first line of text.
http://doc.qt.io/qt-5/qml-qtquick-text.html#lineLaidOut-signal
Here's what I did:
Text {
text: issue.summary
onLineLaidOut: {
if (line.number == 0) {
var indent = tagRect.width + tagRect.rightMargin
line.x += indent
line.width -= indent
}
}
Rectangle {
id: tagRect
implicitWidth: padding + tagText.implicitWidth + padding
implicitHeight: padding + tagText.implicitHeight + padding
color: "#400"
property int padding: 2
property int rightMargin: 8
radius: 3
Text {
id: tagText
anchors.centerIn: parent
text: issue.product
color: "#fff"
}
}
}

Related

Formatting issues with the virtual list in Vaadin 23

I have created a VirtualList to display books information. My renderer presents each book information as an accordion, where first panel is generic book information (author, title, etc.) and it might have three more panels: annotation, commentary, reviews.
It is working as expected, but I do have formatting problems.
Here is my book renderer:
private final ComponentRenderer<Component, Book> bookRenderer = new ComponentRenderer<>(
book -> {
//info panel
Accordion bookPresentation = new Accordion();
HorizontalLayout bookInfo = new HorizontalLayout();
bookInfo.getStyle().set("background-color", book.getHighlight());
bookInfo.setWidth("100%");
UnorderedList authorsList = createAuthorsList(book.getAuthors());
authorsList.setMinWidth("24%");
VerticalLayout taggedTitle = createTaggedTitle(book);
VerticalLayout stars = createStarRating(book);
VerticalLayout readerRating = createReaderRating(book);
VerticalLayout bookStatus = createStatusField(book);
bookInfo.add(authorsList, taggedTitle, stars, readerRating, bookStatus);
AccordionPanel summary = new AccordionPanel(bookInfo);
summary.addThemeVariants(DetailsVariant.SMALL);
summary.addClassName("book-item");
bookPresentation.add(summary);
//annotation panel
if (book.getAnnotation() != null && !book.getAnnotation().isBlank()) {
TextArea annotation = new TextArea();
annotation.setWidth("75%");
annotation.setValue(book.getAnnotation());
annotation.setReadOnly(true);
annotation.setMaxHeight(MAX_HIGHT);
bookPresentation.add("Аннотация", annotation);
}
if (book.getCommentary() != null && !book.getCommentary().isBlank()) {
TextArea comment = new TextArea();
comment.setValue(book.getCommentary());
comment.setReadOnly(true);
comment.setMaxHeight(MAX_HIGHT);
bookPresentation.add("Комментарий", comment);
}
if (book.getReadersReviews() != null && !book.getReadersReviews().isEmpty()) {
Accordion reviews = new Accordion();
for (ReadersReview review : book.getReadersReviews()) {
TextArea reviewText = new TextArea();
reviewText.setMaxHeight(MAX_HIGHT);
reviewText.setValue(review.getReaderComment());
if(user == null || !user.getReaderName().equalsIgnoreCase(review.getReviewerName())) {
reviewText.setReadOnly(true);
}
reviews.add(review.getReviewerName(), reviewText);
}
bookPresentation.add("Мнение читателей", reviews);
}
return bookPresentation;
});
And here is what I am getting on the screen:
I have separation line between accordion panels, but no separation between two items (books) in the list. I'd prefer to have it otherwise. At least I do need to separate one book from another. I did search Vaadin documentation but didn't find any settings that can display such separator between VirtualList items. I'd like to have this separator to be of a different color and line width than accordion panel separator. I followed Joel advise and added border to the styles.css file and added CSS class to the accordion or to its first panel, but it didn't have any effect.
The gap between items in the displayed VirtualList varies significantly as it can be seen on the screenshot. Sometimes items go one after another, and sometimes distance between them can reach 2+ inches. How can I set a fixed distance between these items?
I have set the width of bookInfo panel to 100% but as you can see, it occupies significantly less. I also specify the width of every component and sum of them should be equal to 100%, but as you can see they aren't aligned. What I am missing there?
To put a separator line between items, you'll want to use CSS. One way to do it is to set a CSS class name on your VirtualList items (via your renderer), say "book-item", and then use some CSS like the following:
.book-item {
border-top: 3px solid darkgray;
}
.book-item:first-of-type {
border-top: none;
}

How to indent multiline text behind lists in QMLs TextEdit

In QML Im using a TextEdit item for a text editor with highlighter code running behind it (QSyntaxHighlighter). When the user types a dash (-), it will be recognised by the highlighter code and formatted (like Markdown). But additionally I want the text to be indented behind the dash, when it is multiline. Just like it behaves with HTML lists.
This is how it looks like right now:
This is how I want it (the text aligns properly behind the dash):
I know this can indent the text:
QTextCursor cursor(currentBlock());
QTextBlockFormat textBlockFormat = currentBlock().blockFormat();
textBlockFormat.setIndent(1);
cursor.setBlockFormat(textBlockFormat);
An idea is to indent all the text by default and un-indent the lines with a dash or similar, but couldn't quite figure out yet how to achieve it.
Any other ideas?
Ok apparently there is also a list styling option. This is how you can change the style of a block:
QTextBlock block = textDoc->findBlockByNumber(i);
QTextCursor cursor(block);
cursor.beginEditBlock();
QTextListFormat::Style style = QTextListFormat::ListDecimal;
QTextBlockFormat blockFmt = cursor.blockFormat();
QTextListFormat listFmt;
if (cursor.currentList()) {
listFmt = cursor.currentList()->format();
} else {
listFmt.setIndent(blockFmt.indent() + 1);
blockFmt.setIndent(0);
cursor.setBlockFormat(blockFmt);
}
listFmt.setStyle(style);
cursor.createList(listFmt);
cursor.endEditBlock();
This could be used in a slot linking to the contentsChange signal from QTextDocument or in a highlighter.

BlackBerry 10 development- TextField

Guys I am developing the basic app in BlackBerry 10 and I want to get text which is in TextField (in cpp). I am trying to find method for that but not getting the right one .So can anyone please tell me how to get the value from TextField in cpp (not in qml) ???
Well, the first thing you need to do is expose your QML TextField to C++. This is done with an object name property ala:
TextField {
objectName: "myTextField"
...
}
Next, find this child from your C++:
QmlDocument *qml = QmlDocument::create("asset:///my.qml");
Container *root = qml->createRootObject<bb::cascades::Container>(); //or whatever the root control is
TextField *textField = root->findChild<TextField*>("myTextField");
From then on, simply use textField->text().
We have 3 parts in this problem
First on in Qml your text area and bottom when clicked send text area to function in c++
TextField {
id: n2
}
Button {
id: button
text: "send text"
onClicked: {
app.sendtext(n2.text)
}
Second part your c++ function in your ApplicationUI to receive this text
QString ApplicationUI::sendtext(QString txtarea)
{
QString text = txtarea;
return text;
}
Third and final part in your ApplicationUI.h u must make this function INVOKABLE to access it in Qml
so u will need this line
Q_INVOKABLE QString sendtext(QString txtarea);

Set line spacing in QTextEdit

I want to set the line spacing of a QTextEdit.
It's no problem to get that information with
QFontMetrics::lineSpacing();
But how to set that?
I tried with StyleSheets, but that didn't work:
this->setStyleSheet("QTextEdit{ height: 200%; }");
or
this->setStyleSheet("QTextEdit{ line-height: 200%; }");
Partial solution:
Well, I've found a solution - not the way I wanted it, but at least it's simple and it gives nearly my intended behavior, enough for my proof of concept.
On every new line there's some linespacing. But if you just type until the text is automatically wrapped to a new line you wont have line-spacing between this two lines. This hack only works with text blocks, see the code.
Just keep in mind it's brute force and a ugly hack. But it provides some kind of line-spacing to your beautiful QTextEdit. Call it everytime your text changes.
void setLineSpacing(int lineSpacing) {
int lineCount = 0;
for (QTextBlock block = this->document()->begin(); block.isValid();
block = block.next(), ++lineCount) {
QTextCursor tc = QTextCursor(block);
QTextBlockFormat fmt = block.blockFormat();
if (fmt.topMargin() != lineSpacing
|| fmt.bottomMargin() != lineSpacing) {
fmt.setTopMargin(lineSpacing);
//fmt.setBottomMargin(lineSpacing);
tc.setBlockFormat(fmt);
}
}
}
The QFontMetrics contains (per the name) static properties that come from the font file. How wide a capital "C" is, etc. lineSpacing() gets you the natural distance in single-spacing that the person who designed the font encoded into the font itself. If you actually wanted to change that (you don't)...the somewhat complicated story of how is told here:
http://fontforge.sourceforge.net/faq.html#linespace
As for the line spacing in a QTextEdit...it looks (to me) like that is seen as one of the things that falls under Qt's extensibility model for specifying text "layouts":
http://doc.qt.io/qt-4.8/richtext-layouts.html
You would supply your own layout class to the QTextDocument instead of using the default. Someone tried it here but did not post their completed code:
http://www.qtcentre.org/threads/4198-QTextEdit-with-custom-space-between-lines
I know this is an old question, but I've spent a lot of time today trying to solve this for PyQt5 5.15.2. I'm posting my solution in case it is useful to others. The solution is for Python, but should be easily transferable.
The following code will change the line height to 150% for a populated QTextEdit widget in one go. Further editing will pick up the current block format, and continue applying it. I've found it to be very slow for large documents though.
textEdit = QTextEdit()
# ... load text into widget here ...
blockFmt = QTextBlockFormat()
blockFmt.setLineHeight(150, QTextBlockFormat.ProportionalHeight)
theCursor = textEdit.textCursor()
theCursor.clearSelection()
theCursor.select(QTextCursor.Document)
theCursor.mergeBlockFormat(blockFmt)
Applying blockformat to entire document rather than each line works.
QTextBlockFormat bf = this->textCursor().blockFormat();
bf.setLineHeight(lineSpacing, QTextBlockFormat::LineDistanceHeight) ;
this->textCursor().setBlockFormat(bf);
I have translated Jadzia626's code to C++ and it works. Here is the information about setLineHeight()
qreal lineSpacing = 35;
QTextCursor textCursor = ui->textBrowser->textCursor();
QTextBlockFormat * newFormat = new QTextBlockFormat();
textCursor.clearSelection();
textCursor.select(QTextCursor::Document);
newFormat->setLineHeight(lineSpacing, QTextBlockFormat::ProportionalHeight);
textCursor.setBlockFormat(*newFormat);

QML text scroll

I am using C++ and QML to create a nice interface.
I would like to have a "console view", where plenty to text is printed through time.
However, when the text of a text item or webview content grows, the view does not "scroll down".
How can I make the text on the bottom of the text/webview item always stay visible ?
I have tried playing with flickable and the_webview.evaluateJavaScript + window.scrollTo , but I could not get them to do what I want.
This seems a fairly simple piece of UI, but I am having serious troubles to do it with QML.
Thanks for you answer.
Yeah I'd use a Flickable containing a Text object. Whenever you add text to the Text, check its paintedHeight, and adjust the Flickable's contentY if it's got any bigger.
Maybe you should consider using a ListView and have the messages as items in the view. Then you could use ListView::positionViewAtEnd.
funkybro answers inspired my final solution:
function scroll_to_bottom() {
flickabe_item.contentY =
Math.max(0, webview_item.height - flickabe_item.height);
return;
}
Thanks !
My solution to this was to vertically flip both the content and Flickable. This way the text ends the right way up and is naturally anchored to the bottom of the flickable area.
It might be more efficient too since the transform is handled by OpenGL behind the scenes.
Flickable {
id: flick
anchors.fill: parent
contentHeight: text.height
Text {
id: text
width: parent.width
transform: Scale { yScale: -1; origin.y: text.height/2 }
}
transform: Scale { yScale: -1; origin.y: flick.height/2 }
}