Detect text content size in TextField - qt

I'd like to reduce the font size in case user types a long text in a TextField. Is there a way to know how wide the current text is rendered?

As far as I know, it is not possible to do directly. You could, however, cheat by using an invisible item that contains the properties you need:
Text {
id: hiddenText
anchors.fill: tf
text: tf.text
font.pixelSize: tf.font.pixelSize
visible: false
}
TextField {
id: tf
width: 100
height: 60
font.pixelSize: 25
onTextChanged: {
while ((hiddenText.contentWidth > hiddenText.width) || (hiddenText.contentHeight > hiddenText.height)) {
font.pixelSize -= 1
}
}
}
You would have to do the same for scaling up the text, in case the user erases some input. The Text component does not contain borders, so you have to decrease it's width by a few pixels to make it the same size as the TextField as well.
Also, check out TextArea or TextInput. They may fit your need.

Related

Add hint to QML TextField with maximum length?

I have a QML TextField where the user can input a limited amount of characters. Is there a way to add a simple hint like so? It can be inside the input too (as uneditable text, of course), or next to it.
ColumnLayout {
Label {
text: "Short description"
}
TextField {
Layout.fillWidth: true
maximumLength: 30
}
}

How to check the auto alignment of text when Text contains Arabic words

I have created a Rectangle item with Text item in center which acts as Edit box with Cursor item at the end of the text.
So for Orientation to be taken care by QML, I have modified the Text item as
Text
{
id: text_input
font.bold: true
font.pixelSize: 22
color: "white"
text: view.defaultTextField
elide: Text.ElideLeft
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
maximumLineCount: 1
clip: true
anchors{
rightMargin: 10
leftMargin: (textInputField === "") ? 18 : 12
verticalCenter: parent.verticalCenter
}
}
and Cursor image as
Image
{
id: img_cursor
x: (textInputField !== "") ?
(text_input.x + text_input.contentWidth)) : 12
anchors.verticalCenter: parent.verticalCenter
source: "text_cursor.png"
}
Now if textInputField contains Arabic text, TextItem is auto change the orientation from right to left. and english it is changing to start from Left.
Text appending coming in :
Arabic: Left <--Right
English: Left --> Right
But for Cursor position, how i can make the logic to auto detect and change the x position of the cursor based on the text_input orientation direction (Arabic and English).
Arabic isn't always RtoL. Numbers, for example, are written LtoR (just like in English). Also, foreign words would be written LtoR. Conversely, if you add an Arabic word in an English text, text direction will change somewhere. Might be the middle of the line, might be at either end.
That's why a simple trick like calling QFontMetrics.width() will only work for simple cases.
Try QTextLayout instead. QLineEdit uses this code in it's control to figure out the X position of the cursor:
qreal cursorToX(int cursor) const { return m_textLayout.lineAt(0).cursorToX(cursor); }
I created a function to check the alignment of the text. so on arabic change , the Text orientation will be modified automatically.
function isArabicAlignment() {
if(text_input.horizontalAlignment === Text.AlignRight)
return true;
else
return false;
}
So on Text input modifies , i will check the condition and update the cursor position.
x: (textInputField !== "" && isArabicAlignment()) ?
(text_input.x + text_input.contentWidth)) : //Changing Cursor in reverse.

How do you select a character from text in Qt?

I've uploaded a text file and parsed the content into a text object. Eventually I will be passing each character in the string to a rectangle of its own. I am trying to figure out how to pick a character from the string in qml?
For example:
//Assume the text is already parsed from the file and it is stored in the object below
Text {
id:myText
text: "The quick brown fox jumps over the lazy dog"
visible:false
}
// And now i would like to transfer the first letter into a rectangle
Rectangle{
id: firstRect
width:rectText.width
height:rectText.height
color: "yellow"
Text {
id:rectText
text: // How do I set this as the first character 'T'
font.pixelSize: 14
color: "black"
}
}
How can set the rectangle with the character from myText to rectText ?
Eventually I will be setting each character in a rectangle of its own.
It is close to impossible. At least not by using what little font metrics functionality is provided to QML. There is TextMetrics available since Qt 5.4, but for some reason it didn't report the text size accurately, at least for the fonts that I've been using. It may have to do with this issue. I ended up getting accurate results by appending the 字 character to the queried text, and I don't even want to go into detail how I figured that out.
And then, if that happens to work, you only have the text dimensions, but no way to determine the position of that rectangle, since QML text elements can only give you the cursor position, but not the position of any particular character. If you have a single line of text it could be doable - just calculate the width of the preceding text, but for multi line it is a no-go.
You will probably have to take a very different approach. Maybe implement an adapter that presents strings as list models, and you represent every individual character as a QML element in something like a flow view.
But having a discrete visual item for every character will be huge overhead for long text, so if you are going to have such text, you will also have to handle a model proxy that only displays a particular portion of the text.
I can currently think of no other way to get accurate information about the position and size of text character. The API simply doesn't have that functionality.
There is a simple example that is perfectly applicable for short text:
ApplicationWindow {
id: main
width: 640
height: 480
visible: true
property var charlist: textfield.text.split('')
property int currentChar: 0
Column {
TextField {
width: main.width
id: textfield
text: "example text"
}
Flow {
width: main.width
height: 200
Repeater {
model: charlist
delegate: Rectangle {
Text { id: tt; text: modelData; font.pointSize: 20 }
width: tt.width
height: tt.height
color: "red"
border.color: index === currentChar ? "black" : "red"
MouseArea {
anchors.fill: parent
onClicked: {
currentChar = index
var pos = mapToItem(main.contentItem, 0, 0)
info.text = "rectangle x y w h: " + pos.x + ", " + pos.y + ", " + width + ", " + height
}
}
}
}
}
Text {
id: info
}
}
}
You can enter arbitrary text in the text field, which will be represented by a model view that creates an item for every character. Clicking a character will "select" it and will also give you the rectangle values corresponding to its position in the application window.

Resize rectangle depending on whether text wraps or not QML

So I have a rectangle with a label text inside. If the text is too long to fit in one single line, I want to increase the size of the rectangle which should otherwise remain the same.
So far I tried using lineCount, clip, truncated properties from
the qt docs-
The text doesn't fit, and I get ellipses. However, Clip and Truncated always return false. Line count just returns the current line count ignoring wheter it should take more space or not.
Now I'm trying to use contentWidth from here. However this always returns a value that is equal or lesser than the actual width. I thought this should return the total value that it should occupy?
How can I accomplish this?
EDIT
I'm trying something like this, but no matter how long my text is or how truncated it is the content width is always smaller than the width of the label. I got the impression from the qml documentation that contentWidth will take into account even the omitted text.
Rectangle{
id: rec
...
Label{
id: messageText
height: Format.singleLine
text: "this text is very long and should be two lines"
Component.onCompleted: {
if (contentWidth > width){
rec.height = Format.multipleLines
}
}
}
}
SECOND EDIT
I learned that the reason why lineCount was always one was due to creating the object programatically (myRectangle is the rectangle containing the label):
messages.source = Qt.resolvedUrl("myRectangle.qml");
messages.item.message = message;
After the first line the Label was created label was intiliazed with lineCount 1. Then I'll try to change the text which will only be truncated after this point.
How about something like this:
height: lineCount > 1 ? Format.multipleLines : Format.singleLine
If your Rectangle contains only your Label, you could do something like this relying on the wrapMode property of the Label, and the childrenRect.height property of the Rectangle.
Rectangle {
anchors.centerIn: parent
width: 200
height: childrenRect.height
border.width: 1
Label {
id: messageText
width: 200
wrapMode: Text.Wrap
text: "This text is very long very long. Like way too long to fit on a single line."
}
}
Alternatively, if you don't mind having your label contain the rectangle instead as the other way around, you could do:
Label {
id: messageText
anchors.centerIn: parent
width: 200
wrapMode: Text.Wrap
text: "This text is very long very long. Like waaaaay too long to fit on a single line."
Rectangle {
anchors.fill: parent
z: -1
border.width: 1
}
}
I'm not sure if there is a better way to do this, but this code did it for me (this is inside Label):
onContentWidthChanged: {
if((lineCount === 1) && (contentWidth !== 0)){
rec.height = Format.singleLine;
}
else{
rec.height = Format.multipleLines;
}
}
I initialized the height to be Format.multipleLines. Then once the text is accounted for (ContentWidth is not 0) I check if lineCount is one. If so I modify the height to be be format.singleLine.
Note that onContentWidth gets call twice after the component is created. The first time contentWidth will always be 0 (hence the need to check for it).

QML - Dynamically swap the visibility/opacity between overlapping Text and TextArea

I want to have a widget in QML which has combination of the following behaviors:
1) Multi line edit
2) Auto scroll the content as and when I hit newline. (The content on top of the page keeps going up as and when I enter new content at the bottom)
3) Have a placeholder text functionality.
As far as I know, only Text and TextField are having placeholder text property and only TextArea is having a multi line edit plus auto scroll. But If there is any such widget with all the combinations then, my question “Dynamically swap the visibility/opacity between overlapping Text and TextArea “ would be invalid.
In case there is no such widget (I wonder why), I am thinking to have a rectangle which has both Text and TextArea overlapping and based on the below logic I want to have the visibility/opacity/focus on one of them:
If the Text Area is empty (0 characters), then have the Text in the foreground with focus and with the placeholder text “Enter some text”. But as soon as the user starts typing, the Text should lose the focus, opacity and go to background and the TextArea should gain the focus and come to the foreground and start accepting multi line input. Similarly, when TextArea is in the foreground and is empty (0 characters) and when the user click on any other widget outside my Rectangle, the Text should again gain the focus, come to the foreground and display the placeholder again.
I tried to get inspiration from this code, but failed miserably, it would be helpful if anyone can help me with a few lines of code or give me some pointers on how to solve this.
You can implement placeholderText for TextArea the same way Qt does in TextField. The source can be found here: TextField.qml
When you remove all the comments and properties, you basically have a background and on top of that a MouseArea, the placeholderText Text and a TextInput. Since you need to have the placeholder visually below the TextArea, you must have a transparent background:
PlaceholderTextArea.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
Rectangle {
property alias placeholderText: placeholder.text
id: background
width: 640
height: 480
color: "#c0c0c0"
Text {
id: placeholder
anchors.fill: parent
renderType: Text.NativeRendering
opacity: !textArea.text.length && !textArea.activeFocus ? 1 : 0
}
TextArea {
id: textArea
anchors.fill: parent
backgroundVisible: false
}
}
and use your component:
PlaceholderTextArea {
placeholderText: qsTr("Hello World")
anchors.fill: parent
}
Here's an alternative implementation, that works a bit better for me:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
Item
{
property alias placeholderText: placeholder.text
property bool __shouldShowPlaceholderText:
!textArea.text.length && !textArea.activeFocus
// This only exists to get at the default TextFieldStyle.placeholderTextColor
// ...maybe there is a better way?
TextField
{
visible: false
style: TextFieldStyle
{
Component.onCompleted: placeholder.textColor = placeholderTextColor
}
}
TextArea
{
id: placeholder
anchors.fill: parent
visible: __shouldShowPlaceholderText
activeFocusOnTab: false
}
TextArea
{
id: textArea
anchors.fill: parent
backgroundVisible: !__shouldShowPlaceholderText
}
}

Resources