How to reset style for rich text in QML TextArea - qt

If I insert rich text in QML TextArea, it will preserve that style (text color, underline etc.) for further inputs. Is there a way to prevent that from happening?
Example:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
TextArea {
id: textArea
textFormat: Qt.RichText
focus: true
selectByMouse: true
selectByKeyboard: true
Keys.onPressed: {
if (event.text === "#") {
textArea.text += "<a href='mentioned://user'>#User</a>";
event.accepted = true
}
}
}
}
When I enter "#" character it will insert the <a> link instead, but when I continue typing other characters it will append them to the link, as if they are a part of it. How can I continue typing a new paragraph, without appending to the link ( tag)?

Adding a whitespace after the tag solves the problem :
Window {
width: 640;
height: 480;
visible: true;
title: qsTr("Code from #Meliodas on StackOverflow");
TextArea {
id: textArea;
textFormat: Qt.RichText;
focus: true;
selectByMouse: true;
selectByKeyboard: true;
Keys.onPressed: (event) => {
if (event.text === "#") {
// HERE
insert(cursorPosition, "<a href='mentioned://user'>#User</a> ");
event.accepted = true;
}
}
}
}
Note that I took the freedom of modifying other pieces of your code :
insert() instead of text += to avoid automatic newlines
explicitly defining event to silence a warning telling me implicit event parameters are deprecated

Related

QML TextArea onEditingFinished On Enter

The QML TextArea inherits onEditingFinished From TextEdit where it is triggered on losing focus or an enter/return press. (assuming no input validation)
However one cannot trigger onEditingFinished from a TextArea with Enter unlike for TextEdit as it is captured for a line break.
Additionally in a window with a single field it can be a unintuitive to remove focus from the TextArea as background and most other Controls don't accept focus so the user has to click off the window or clicking the menu bar.
How can I improve the user experience for triggering actions once they have edited a multiline string input field? Is linebreaks inputted with Ctrl+Enter, with editing completion on Enter an option, and if so how would this be implemented?
Here is the example QML of this scenario:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
Window {
visible: true
width: 640; height: 480
title: qsTr("Hello World")
Column {
TextArea {
implicitHeight: 200
placeholderText: qsTr("Enter description")
onEditingFinished: console.log("Editing complete")
}
Label {text: qsTr("Label")}
}
}
You can override the Return key press event and handle it however you want. If you want to use the Ctrl+Return, check the modifiers property in the event.
TextArea {
implicitHeight: 200
placeholderText: qsTr("Enter description")
onEditingFinished: console.log("Editing complete")
Keys.onReturnPressed: {
if (event.modifiers & Qt.ControlModifier) {
// Ctrl+Return
editingFinished();
}
else {
// Ignore other cases
event.accepted = false;
}
}
}
Or if you want to use the Return key without pressing Ctrl, then you can simply do this:
TextArea {
implicitHeight: 200
placeholderText: qsTr("Enter description")
onEditingFinished: console.log("Editing complete")
Keys.onReturnPressed: {
editingFinished();
}
}
You can do it by handling the simple ReturnPressed and ctrl+ReturnPressed or Shift+ReturnPressed event by yourself.
In the code below I've used Shift+ReturnPressed for new line:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
Window {
visible: true
width: 640; height: 480
title: qsTr("Hello World")
Column {
TextArea {
id: text_area
implicitHeight: 200
placeholderText: qsTr("Enter description")
Keys.onReturnPressed: {
if(Qt.ShiftModifier)
console.log("Editing complete")
else {
text += '\n'
text_area.cursorPosition = text_area.length
}
}
}
Label {text: qsTr("Label")}
}
}
Based on the answers by Jarman (https://stackoverflow.com/a/69723941/1581658) and Farshid616 (https://stackoverflow.com/a/69724074/1581658) I came up with this
TextArea {
implicitHeight: 200
placeholderText: qsTr("Enter description")
onEditingFinished: console.log("Editing complete")
Keys.onReturnPressed: {
if(event.modifiers & Qt.ControlModifier) {
insert(cursorPosition, "\n")
}
else {
editingFinished()
}
}
}
This specifically allows for a newline to be inserted at the current cursor when Ctrl+Enter is pressed, and for editingFinished to be called when a non-modified Enter is pressed

QML Video element randomly disappears on Label text change

I'm working on an app which is supposed to display a camera stream from a webcam. It's a single-page app which only shows a Video QML element showing the stream (which currently is a simple .avi file) and a Label element indicating the current connection state from an MQTT connection.
Here's the code:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.1
import QtMultimedia 5.12
import MqttClient 1.0
Window {
width: 360
height: 640
visible: true
title: qsTr("Doorbell")
MqttClient {
id: client
// TODO
property string _host: "localhost"
property string _port: "1883"
property string _topic: "my/topic"
hostname: _host
port: _port
Component.onCompleted: {
connectToHost()
}
onConnected: {
subscribe(_topic)
}
onMessageReceived: {
video.play()
}
Component.onDestruction: {
disconnectFromHost()
}
}
GridLayout {
anchors.fill: parent
anchors.margins: 10
columns: 2
Video {
id: video
objectName: "vplayer"
width: parent.width
height: 300
Layout.columnSpan: 2
autoPlay: false
source: "file:///path/to/my/test.avi"
onErrorChanged: {
console.log("error: " + video.errorString)
}
MouseArea {
anchors.fill: parent
onClicked: {
video.muted = !video.muted
}
}
focus: true
Image {
id: muteIndicator
source: "mute_white.png"
width: 64
height: width
visible: video.muted
anchors.centerIn: parent
}
}
Label {
function stateToString(value) {
if (value === 0)
return "Disconnected"
else if (value === 1)
return "Connecting"
else if (value === 2)
return "Connected"
else
return "Unknown"
}
Layout.columnSpan: 2
Layout.fillWidth: true
text: stateToString(client.state) + "(" + client.state + ")"
}
}
}
Here's a screenshot:
Here's the clue:
I tried removing the Label and replacing it with a simple rectangle indicator (red or green) to show if the connection is currently active.
However, when replacing the text content of the Label element, the Video element completely disappears.
What I've tried:
removing the stateToString(client.state) + "(" + client.state + ")" part and replacing it with text: "Connected(2)"
replacing stateToString(... with an empty string (text: "")
replacing the content of stateToString(...) with return "Connected(2)"
and a lot of different more seemingly completely useless things
Example code:
// ...
Label {
function stateToString(value) {
if (value === 0)
return "Disconnected"
else if (value === 1)
return "Connecting"
else if (value === 2)
return "Connected"
else
return "Unknown"
}
Layout.columnSpan: 2
Layout.fillWidth: true
text: "Connected(2)"
// enabled: client.state === MqttClient.Connected
}
// ...
Unless I set text to the exact value stateToString(client.state) + "(" + client.state + ")" or at least stateToString(client.state), the Video element will always disappear in the QML view:
I have no idea of what might be the reason for this.
Any help is greatly appreciated, thanks.
Try adding:
Layout.fillWidth: true
after:
objectName: "vplayer"
I'm 99% sure the root of the problem is in the way you have used the layouts. In another word, you have not placed your QML elements in a robust way. since QML has so much flexibility in positioning elements in UI, it matters in QML to put your elements in a well-defined, robust way.

QML prevent ListView delegate to update all the time

I'm relatively new to QML/QtQuick and still learning. I have a little performane issue with a very small private project. I just tryed to implement a filter function to my ListView, because >15.000 objects are a lot to search manually. I just want to update the ListView when I finished the editing of my search field or pressing "return". But instead it's refreshing every time I insert or delete a character from this textfield which needs sometimes a few seconds.
Anyone have an idea how to prevent the list to be refreshed permanently or reducing theese performance issues?
Thanks a lot
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.XmlListModel 2.12
import Anime_initialiser 1.0
import "."
Page {
TextField{
id: searchField
width: parent.width
z: 1
/*onEditingFinished: {
XL.animeListModel.reload()
}*/
}
ListView {
z: 0
ScrollBar.vertical: ScrollBar { active: true }
id: listView
width: parent.width
height: parent.height
model: XL.animeListModel
y: searchField.height
Anime_initialiser {
id: initialiser
onShowAnimeDetails: {
xmlDataString = xmlString
swipeView.currentIndex = swipeView.currentIndex+1
}
}
delegate: ItemDelegate {
visible: {
if (searchField.length > 0)
return (main_title.toLowerCase().match(searchField.text.toLowerCase()) || de_title.toLowerCase().match(searchField.text.toLowerCase())) ? true : false
else
return true
}
height: visible ? Button.height : 0
width: parent ? parent.width : 0
Button {
anchors.fill: parent
onClicked: {
anime_id = aid
initialiser.buttonClicked(anime_id)
}
Text {
width: parent.width
height: parent.height
font.pointSize: 100
minimumPointSize: 12
fontSizeMode: Text.Fit
text: aid + ": " + main_title + (de_title ? "\nDE: " + de_title : "")
}
}
}
}
}
Rather than toggling the visible flag of all of your delegates, you should use a QSortFilterProxyModel. The idea is that the proxy model would use your XL.animeListModel as a source model, and then you can give the proxy a regular expression telling it which ones to filter out. Depending on how you want it to filter, you could just call setFilterRole() to tell it which property to compare against your regex, or you could do a custom filter by overriding the filterAcceptsRow() function.
EDIT:
If you don't want to use a proxy, you can still prevent the constant updates by not binding on the visible property directly to the search field. You were on the right track with your onEditingFinished code. You could create a separate text string that just holds the completed search text.
property string searchText: ""
Then update that string when you are done typing your search text.
onEditingFinished: {
searchText = searchField.text.toLowerCase();
}
And finally, bind your visible property to this new string.
visible: {
if (searchText.length > 0)
return (main_title.toLowerCase().match(searchText) || de_title.toLowerCase().match(searchText)) ? true : false
else
return true
}

Qt5/QML - Pagination in Text/TextArea

Let's say I have a QML Text or TextArea that contains a very long HTML page. I want to make it easier to read by splitting it into pages.
More specifically, every time I press the down key, I want it to scroll down until none of the current text on the screen is still there.
I already know how to make the entire TextArea scrollable; that's not what I'm looking for. What I'm looking for is more like the kind of behavior you'd expect in an ebook reader.
Is there any way to do something like that, preferably in pure QML (though C++ is also fine).
You can measure the TextArea height after loading, divide it to the container height and so get the count of pages. Then you just move the TextArea relative to its container according to the current page.
The simple illustration of my idea:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
Window {
id: main
visible: true
width: 640
height: 800
property int currentPage: 1
property int pageCount: 1
ColumnLayout
{
anchors.fill: parent
anchors.margins: 5
RowLayout {
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignHCenter
Button {
text: "<"
onClicked: {
if(main.currentPage > 1)
main.currentPage --;
}
}
Text {
text: main.currentPage + " / " + main.pageCount
}
Button {
text: ">"
onClicked: {
if(main.currentPage < main.pageCount)
main.currentPage ++;
}
}
}
Rectangle {
id: container
clip: true
Layout.fillHeight: true
Layout.fillWidth: true
TextArea {
id: msg
text: "Loading ..."
width: container.width
height: container.height
y: -(main.currentPage - 1) * container.height
textFormat: TextEdit.RichText
wrapMode: TextEdit.Wrap
Component.onCompleted: {
msg.makeRequest();
}
onContentHeightChanged: {
msg.height = msg.contentHeight;
if(msg.contentHeight >= container.height && container.height > 0)
{
main.pageCount = msg.contentHeight / container.height;
loader.running = false;
}
}
function makeRequest()
{
var doc = new XMLHttpRequest();
msg.text = "";
doc.onreadystatechange = function() {
if (doc.readyState === XMLHttpRequest.DONE) {
msg.text = doc.responseText;
}
}
doc.open("GET", "http://www.gutenberg.org/files/730/730-h/730-h.htm");
doc.send();
}
}
}
}
BusyIndicator {
id: loader
running: true
anchors.centerIn: parent
}
}
Of course you have to process margins, the last line on a page, recalculate the values on resizing etc.

get cursor position in WebEngineView qml

I have a program in Qt and a WebEngineView in it .I want to when my user clicked on a inputbox in webEngine a keyboard have been loaded and the inputbox get its contents from my keyboard (i wrote my own keyboard) but i can't do it .i try codes in bellow but don't work
WebEngineView {
anchors.fill:parent
id:webEng
url: "https://example.com"
visible: true
MouseArea {
id : mousearea
anchors.fill: parent
onClicked: {
mykeyboard.visible=true;
}
}
}
This is not a complete answer but this code could help:
import QtQuick 2.10
import QtWebView 1.1
import QtQuick.Controls 1.4
import QtWebEngine 1.7
Item {
width: 1280
height: 720
WebView { // or WebEngineView {
id: webview
width: 1280
height: 720
url: "http://google.com"
visible: true
onLoadingChanged: {
if (loadRequest.status === WebView.LoadSucceededStatus) {
console.log("Loaded!!")
webview.runJavaScript('
var input = document.getElementById("lst-ib");
input.addEventListener("click", function() {
alert("Clicked!");
});
'
)
}
}
}
Rectangle {
id: myDummyKeyboard
anchors.bottom: parent.bottom
width: parent.width
height: 100
color: "gray"
visible: true
border.width: 20
Button {
anchors.centerIn: parent
text: "Dummy"
onClicked: {
webview.runJavaScript('document.getElementById("lst-ib").value += "' + text + '"');
}
}
}
}
The part in the WebView (or WebEnginView) allows to display an alert when the input is clicked. But, something is missing, to link it to a QML handler. The solution is maybe to use WebChannel or maybe WebEngineScript as said by #folibis in the comments.
The part defined by myDummyKeyboard allows to add a string into the input when the user is clicking the button in the rectangle (fake keyboard).

Resources