Modify a QTextDocument of a QML TextArea element - qt

I'm trying to create a minimal version of the following example: https://doc.qt.io/qt-5/qtquickcontrols-texteditor-example.html
I have created a DocumentHandler class with the following method:
class DocumentHandler : public QObject {
Q_OBJECT
QML_ELEMENT
public:
Q_INVOKABLE void changeFormat(QQuickTextDocument *doc) {
QTextCursor cursor(doc->textDocument());
cursor.select(QTextCursor::Document);
QTextCharFormat format;
format.setFontItalic(true);
cursor.mergeBlockCharFormat(format);
}
}
Unfortunately, when I call this method from the following QML code
import DocumentTest 1.0
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Document Test")
TextArea {
id: textArea
focus: true
anchors.fill: parent
wrapMode: Text.Wrap
selectByMouse: true
text: "Another day in paradise"
onReleased: {
document.changeChar(textDocument);
}
textFormat: Text.RichText
}
DocumentHandler {
id: document
}
}
I have a crash at the following line: https://github.com/qt/qtbase/blob/5.15.2/src/gui/text/qtextoption.h#L118
I read in the documentation that the QQuickTextDocument::textDocument() is read-only and that I can not modify its internal state so I wonder if using the QTextCursor is the right way to go. This is what is done in the example but I struggle finding out what is the difference with my code.

Looking at https://doc.qt.io/qt-5/qml-qtquick-textedit.html#textFormat-prop, i would replace textFormat: Text.RichText with textFormat: TextEdit.RichText

Sometime ago, I wrote a wrapper for the QSyntaxHighter classes. This achieves more than you require, but, it has the following elements:
control text color and formatting in your TextEdit
link to the TextEdit's textDocument property
Note that the example works with TextEdit not TextArea.
#include <QObject>
#include <QTextDocument>
#include <QSyntaxHighlighter>
#include <QQuickTextDocument>
class SyntaxHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
Q_PROPERTY(QQuickTextDocument* textDocument READ textDocument WRITE setTextDocument NOTIFY textDocumentChanged)
public:
SyntaxHighlighter(QObject* parent = nullptr);
Q_INVOKABLE void setFormat(int start, int count, const QVariant& format);
// ...
};
You can use it like this:
TextEdit {
id: textEdit
}
SyntaxHighlighter {
textDocument: textEdit.textDocument
onHighlightBlock: {
let rx = /[A-Za-z]/g;
let m;
while ( ( m = rx.exec(text) ) !== null ) {
let keywords = [
'import', 'function', 'bool', 'var',
'int', 'string', 'let', 'const', 'property',
'if', 'continue', 'for', 'break', 'while',
];
if (keywords.includes(m[0])) {
setFormat(m.index, m[0].length, keywordFormat);
continue;
}
}
}
}
TextCharFormat {
id: keywordFormat
foreground: "#808000"
font.pointSize: 12
font.bold: true; font.italic: true
}
For full source code, refer to:
https://github.com/stephenquan/QtSyntaxHighlighterApp

Related

How to trigger a .sh file or a bash command on a Qt4 button click?

Right now, all I have is my QML file, with the button.
/*PlasmaComponents.*/ToolButton {
id: shutdownButton
text: i18n("Shutdown")
iconSource: "system-shutdown"
enabled: power.canShutdown
onClicked: doTheThing();
}
From reading about QML, it seems I'll need to add a C++ process. Is this possible with QML4? If not, could QProcess work? What files would need changing if so?
you can write a process executor class like this :
#include <QProcess>
#include <QVariant>
class Process : public QProcess {
Q_OBJECT
public:
Process(QObject *parent = 0) : QProcess(parent) { }
Q_INVOKABLE void start(const QString &program, const QVariantList &arguments) {
QStringList args;
// convert QVariantList from QML to QStringList for QProcess
for (int i = 0; i < arguments.length(); i++)
args << arguments[i].toString();
QProcess::start(program, args);
}
Q_INVOKABLE QByteArray readAll() {
return QProcess::readAll();
}
};
and register them :
#include <QtQml>
#include "process.h"
qmlRegisterType<Process>("Process", 1, 0, "Process");
and finally run your command from QML :
import QtQuick 2.4
import QtQuick.Controls 1.3
import Process 1.0
ApplicationWindow {
width: 800
height: 480
visible: true
Text {
id: text
}
Process {
id: process
onReadyRead: text.text = readAll();
}
Timer {
interval: 1000
repeat: true
triggeredOnStart: true
running: true
onTriggered: process.start("poweroff", [ "-f" ]);
}
-f make forced to power off immediately.

QML not getting property from singleton in another file

I created a project that puts some QML in a plugin, and an app using Qt's QPluginLoader.
The app's QML can then use the plugin's QML, with one noticeable problem. From one of the plugin's QML files, I am not able to read a property of a singleton in another QML file. If you look in the file hello.qml, there is a Text item that tries to get his text from MySingleton, but that text does not show up. When I substitute a string literal (the commented out line), the text shows up just fine.
I've got the project here: https://github.com/rhvonlehe/qmlplugin
The project itself is very basic, but represents a lot of the same things going on in a larger project that I can't share but has the same problem.
Output as-is:
In the blue rectangle there should be the words, "singleton Text"
The QtCompany provided the answer, which I'm able to share here.
Long story short, to get the full functionality expected from QML in a plugin I needed to use QQmlExtensionPlugin. Here are the updated files to make things work (Plugin.h and hello.qml)
hello.qml
import QtQuick 2.0
import org.example.PluginInterface 1.0
import "."
Rectangle {
id: page
width: 320; height: 240
color: "lightgray"
Text {
id: helloText
text: "Hello world!"
y: 30
anchors.horizontalCenter: page.horizontalCenter
font.pointSize: 24; font.bold: true
}
Rectangle {
id: innerPage
width: 300; height: 30
anchors.horizontalCenter: page.horizontalCenter
color: "blue"
Text {
id: nextOne
color: "white"
// text: "literal Text"
text: MySingleton.getTheText()
anchors.verticalCenter: innerPage.verticalCenter
anchors.horizontalCenter: innerPage.horizontalCenter
font.pointSize: 18; font.bold: false
}
}
OtherThing {
id: myOtherThing
x: 20; y: 150
}
}
Plugin.h
#ifndef PLUGIN_H
#define PLUGIN_H
#include <PluginIf.h>
#include <QQmlExtensionPlugin>
#include <QQmlEngine>
class Q_DECL_EXPORT Plugin : public QQmlExtensionPlugin, PluginInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
Q_INTERFACES(PluginInterface)
public:
void init() override;
void registerTypes(const char *uri) override
{
qmlRegisterSingletonType(QUrl("qrc:/MySingleton.qml"), uri, 1, 0, "MySingleton");
}
};
#endif // PLUGIN_H

QtQuick's ListView fails to take ownership of QAbstractItemModel object

Based on Qt documentation, whenever a QObject pointer type is passed from C++ code to QML, via a Q_INVOKABLE method, there is a set of rules that determine who is responsible for the lifetime of that pointer. Should the QObject be parentless, implicitly the QML engine is responsible for taking ownership of the pointer.
In my scenario, I want my frontend UI to represent a list model which is generated/provided by the backend C++ code. My assumption is that the pointer will stay alive as long as there is a reference to it by the QML code. The code below shows the trimmed down test case:
Main.cpp
#include <QAbstractItemModel>
#include <QDebug>
#include <QGuiApplication>
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QStringListModel>
class MyStringListModel : public QStringListModel
{
Q_OBJECT
public:
explicit MyStringListModel(const QStringList &strings, QObject* parent=nullptr) : QStringListModel(strings, parent)
{
qDebug() << "Creation";
}
virtual ~MyStringListModel() override
{
qDebug() << "Destruction";
}
};
class Backend : public QObject
{
Q_OBJECT
public:
Backend(QObject* parent=nullptr) : QObject(parent)
{
}
Q_INVOKABLE QAbstractItemModel* createModel() const
{
static const QStringList months = {
tr("January"),
tr("February"),
tr("March"),
tr("April"),
tr("May"),
tr("June"),
tr("July"),
tr("August"),
tr("September"),
tr("October"),
tr("November"),
tr("December"),
};
return new MyStringListModel(months);
}
};
int main(int argc, char* argv[])
{
QGuiApplication application(argc, argv);
qmlRegisterType<QAbstractItemModel>();
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
engine.load("qrc:///ui/main.qml");
return application.exec();
}
#include "main.moc"
Main.qml
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.1
ApplicationWindow {
id: window
width: 200
height: 250
visible: true
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: backend.createModel()
delegate: Text {
anchors.horizontalCenter: parent.horizontalCenter
text: model.display
}
}
Button {
Layout.alignment: Qt.AlignCenter
text: qsTr("Garbage Collect")
onClicked: gc()
}
}
}
This is a screenshot of the program:
The moment the user clicks on the button, the garbage collector runs and destroys the model ptr (destruction is evident by the "Creation" and "Destruction" output in the stdout).
I'm curious to know why the pointer was destroyed? I've noticed that it didn't set the ListView as its parent, which is fair enough, I thought that the QML engine would have used some form of reference pointer to try keep track of who still holds a reference to it. Is there a document which gives greater insight into the way in which garbage collection / ownership is implemented.
Likewise, is there a better way of structuring this code while still meeting the demands of passing a parentless QObject back to QML.
It seems that the reason for the destruction is because the object is not being referenced in QML, for example if it is assigned to a property the garbage collector will not affect it:
ApplicationWindow {
id: window
width: 200
height: 250
visible: true
property var mymodel: backend.createModel()
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: mymodel
delegate: Text {
anchors.horizontalCenter: parent.horizontalCenter
text: display
}
}
Button {
Layout.alignment: Qt.AlignCenter
text: qsTr("Garbage Collect")
onClicked: gc()
}
}
}

C++ class exposed to QML error in fashion TypeError: Property '...' of object is not a function

I've successfully exposed a C++ class to QML. It is registered and found in Qt Creator. It's purpose is to connect to a database, as shown in following code:
#ifndef UESQLDATABASE_H
#define UESQLDATABASE_H
#include <QObject>
#include <QtSql/QSqlDatabase>
class UeSqlDatabase : public QObject
{
Q_OBJECT
Q_PROPERTY(bool m_ueConnected READ isConnected WRITE setConnected NOTIFY ueConnectedChanged)
private:
bool m_ueConneted;
inline void setConnected(const bool& ueConnected)
{ this->m_ueConneted=ueConnected; }
public:
explicit UeSqlDatabase(QObject *parent = 0);
Q_INVOKABLE inline const bool& isConnected() const
{ return this->m_ueConneted; }
~UeSqlDatabase();
signals:
void ueConnectedChanged();
public slots:
void ueConnectToDatabase (const QString& ueStrHost, const QString& ueStrDatabase,
const QString& ueStrUsername, const QString& ueStrPassword);
};
#endif // UESQLDATABASE_H
However, when I try to call method isConnected() from the following QML code
import QtQuick 2.0
Rectangle
{
id: ueMenuButton
property string ueText;
width: 192
height: 64
radius: 8
states: [
State
{
name: "ueStateSelected"
PropertyChanges
{
target: gradientStop1
color: "#000000"
}
PropertyChanges
{
target: gradientStop2
color: "#3fe400"
}
}
]
gradient: Gradient
{
GradientStop
{
id: gradientStop1
position: 0
color: "#000000"
}
GradientStop
{
position: 0.741
color: "#363636"
}
GradientStop
{
id: gradientStop2
position: 1
color: "#868686"
}
}
border.color: "#ffffff"
border.width: 2
antialiasing: true
Text
{
id: ueButtonText
color: "#ffffff"
text: qsTr(ueText)
clip: false
z: 0
scale: 1
rotation: 0
font.strikeout: false
anchors.fill: parent
font.bold: true
style: Text.Outline
textFormat: Text.RichText
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 16
}
MouseArea
{
id: ueClickArea
antialiasing: true
anchors.fill: parent
onClicked:
{
uePosDatabase.ueConnectToDatabase("127.0.0.1",
"testDb",
"testUser",
"testPassword");
if(uePosDatabase.isConnected()==true)
{
ueMenuButton.state="ueStateSelected";
}
else
{
ueMenuButton.state="base state"
}
}
}
}
I get the following error:
qrc:/UeMenuButton.qml:92: TypeError: Property 'isConnected' of object UeSqlDatabase(0x1772060) is not a function
What am I doing wrong?
You have this error because you have declared the property isConnected in C++ but you're calling it from QML in the wrong way: uePosDatabase.isConnected is the correct way, not uePosDatabase.isConnected().
If you want to call the function isConnected() you should change its name to differate it from the property, like getIsConnected(). Given your property declaration, you neither need to call this function directly nor you need to make it callable from QML with the Q_INVOKABLE macro.
You must first create the object you want in QML code and then use the function:
your QML Code:
import QtQuick 2.0
import QSqlDatabase 1.0
Rectangle {
id: ueMenuButton
QSqlDatabase {
id: uePosDatabase
}
.
.
.
MouseArea
{
id: ueClickArea
antialiasing: true
anchors.fill: parent
onClicked: {
console.log(uePosDatabase.isConnected())
}
}
}
Be aware in case of an error some lines earlier in the same .js file can lead to this problem. The following code will not be executed.

How to insert ListElement in a QML ListModel using C++?

Well, i'm learning to work with QML and i have one doubt. In my example, i have a ListModel with ListElements at QML and i have a QML main file with a rectangle, PathView etc.
I have a QWidget also, than is my main window. In this QWidget i include the QML UI like a component. Ok!
How can I handle QML ListElements by using C++?
Note: when I say "to handle", I want to say include an element for example.
Below are some parts of my code...
QML containing my ListElement, called "Menu1":
import QtQuick 1.0
ListModel {
id: listMovieModel
ListElement { name: "Image 1"; iconSource: "pics/image_1.jpg" }
ListElement { name: "Image 2"; iconSource: "pics/image_2.jpg" }
ListElement { name: "Image 3"; iconSource: "pics/image_3.jpg" }
ListElement { name: "Image 4"; iconSource: "pics/image_4.jpg" }
ListElement { name: "Image 5"; iconSource: "pics/image_5.jpg" }
ListElement { name: "Image 6"; iconSource: "pics/image_6.jpg" }
}
My main QML:
Rectangle {
width: 500
height: 600
color: "transparent"
PathView {
id: view
focus: true
width: parent.width
height: parent.height + y
y: -150
model: Menu1 {} //First QML showed
delegate: Image {
source: iconSource
width: 64
height: 90
scale: PathView.isCurrentItem ? 3.5 * y / parent.height : 2.0 * y / parent.height
z: y
smooth: true
}
path: MyGeometricFigure { //This a another file, but is confidential
width: view.width
height: view.height
}
preferredHighlightBegin: 0
preferredHighlightEnd: 0
highlightRangeMode: PathView.StrictlyEnforceRange
Keys.onLeftPressed: decrementCurrentIndex()
Keys.onRightPressed: incrementCurrentIndex()
}
}
And as I use QML like a component for my QWidget:
MainForm::MainForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::MainForm)
{
ui->setupUi(this);
this->resize(1024, 576);
QDeclarativeView *myQMLTest = new QDeclarativeView(QUrl::fromLocalFile("myMainQML.qml"));
myQMLTest->setStyleSheet(QString("background: transparent; width: 600px"));
this->ui->frameListVideoGallery->layout()->addWidget(myQMLTest);
myQMLTest->setFocus();
myQMLTest->installEventFilter(this);
}
I saw some articles about this, but I am not able to change my LisModel using C++. I saw here http://doc.qt.nokia.com/4.7/qdeclarativemodels.html#c-data-models and here in examples using PathView http://doc.qt.nokia.com/4.7/qdeclarativeexamples.html
Can someone help me?
Thanks!
Alright. I think you can do something like this:
In your main QML-file add simple function.
Rectangle {
// ...
function append(newElement) {
view.model.append(newElement)
}
PathView {
id: view
// ...
model: Menu1 {} //First QML showed
// ...
}
}
This method will just call a corresponding method from ListModel. More supported methods you can find there.
Then all you need is to call this method from C++ code. You can do this in such manner:
MainForm::MainForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::MainForm)
{
ui->setupUi(this);
this->resize(1024, 576);
QDeclarativeView *myQMLTest = new QDeclarativeView(QUrl::fromLocalFile ("myMainQML.qml"));
myQMLTest->setStyleSheet(QString("background: transparent; width: 600px"));
QVariantMap newElement; // QVariantMap will implicitly translates into JS-object
newElement.insert("name", "Image 13" );
newElement.insert("iconSource", "pics/image_13.jpg");
QMetaObject::invokeMethod(
myQMLTest->rootObject(), // for this object we will call method
"append", // actually, name of the method to call
Q_ARG(QVariant, QVariant::fromValue(newElement)) // method parameter
);
this->ui->frameListVideoGallery->layout()->addWidget(myQMLTest);
myQMLTest->setFocus();
myQMLTest->installEventFilter(this);
}
You should check this for more information.
Also you can choose another approach: to pass some data via qml properties (using QDeclarativeEngine and QDeclarativeContext) and then handle this data and your list-model right in JavaScript code.
I want to make the answer from #GooRoo more complete/usefull to Qt beginners. If you use the Qt Creator, you will start with a template using QmlApplicationViewer. To apply the answer from GooRoo you have to do something like this (thanks to http://www.lothlorien.com/kf6gpe/?p=309):
CPP:
Q_DECL_EXPORT int main(int argc, char *argv[])
{
QScopedPointer<QApplication> app(createApplication(argc, argv));
QScopedPointer<QmlApplicationViewer> viewer(QmlApplicationViewer::create());
viewer->setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer->setMainQmlFile(QLatin1String("qml/mydemo/main.qml"));
viewer->showExpanded();
QMetaObject::invokeMethod(
(QObject *)viewer->rootObject()->findChild<QObject *>("myModel"), // QML item
"test" // name of the method to call
// without parameters
);
return app->exec();
}
QML:
PageStackWindow {
id: mainApp
...
ListModel {
id: myModel
objectName: myModel //THIS IS NEEDED BY findChild()
...
function test() {
console.log("TEST OK");
}
}
Page {
...
}
}
Here is one more complete example of filling data in QML TableView from C++. Please pardon the bad variable names. I was making it for testing purpose only.
I have used QVariantList and filled it with QVariantMap objects.
Here, multiple data has been filled with a for loop in two columns of the QML's table, namely what and session_name by putting them in a QVariantMap and then inserting that QVariantMap in QVariantList.
HH.h
#ifndef HH_H
#define HH_H
#include <QVariant>
class AA : public QObject
{
Q_OBJECT
QVariantList m_gname;
public:
Q_PROPERTY(QVariantList gname READ gname WRITE setGname NOTIFY gnameChanged)
AA()
{
QVariantList q;
for(int i = 0; i < 10; i++)
{
QVariantMap p;
p.insert("what", "qq");
p.insert("session_name", "aa");
q.push_back(p);
}
setGname(q);
}
QVariantList gname() const
{
return m_gname;
}
public slots:
void setGname(QVariantList gname)
{
if (m_gname == gname)
return;
m_gname = gname;
emit gnameChanged(m_gname);
}
signals:
void gnameChanged(QVariantList gname);
};
#endif // HH_H
Here I have use qmlRegisterType to register the class AA which I created above.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "HH.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<AA>("ZZZ", 1, 0, "AA");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
In QML I have created a TableView by QtQuick.Controls 1.4. It has two columns namely what and session_name. I have created an object of the class AA here and on its Component.onCompleted called <model_id> dot append( <QVariantList_name> )
like this:
mymodel.append( gname )
main.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 1.4 as OldControls
import ZZZ 1.0
Window
{
visible: true
width: 1000
height: 1000
title: qsTr("Hello World")
AA
{
Component.onCompleted:
{
mymodel.append(gname)
}
}
Rectangle
{
id: head
anchors.centerIn: parent; height: 500; width: 500; border.color: "red"
OldControls.TableView
{
anchors.fill: parent
verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOn
OldControls.TableViewColumn
{
role: "what"
title: "What"
}
OldControls.TableViewColumn
{
role: "session_name"
title: "Session Name"
}
model: ListModel
{
id: mymodel
}
}
}
}

Resources