Reputation: 487
I wanted to run qt QSyntaxHighlight example, but building window with QML, not with python code. While I still managed to do so code is awkard and I believe that either my QML or API understanding is wrong.
I created simple QML file with to TextEdits named main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello")
TextEdit {
id: text_view
objectName: "text_view"
x: 0
y: 0
width: parent.width
height: parent.height * 0.8
color: "black"
text: qsTr("long class")
font.pixelSize: 12
anchors.margins: 5
}
TextEdit {
id: text_input
anchors.top: text_view.bottom
width: parent.width
height: parent.height - text_view.height
color: "#ef1d1d"
text: qsTr("")
font.capitalization: Font.MixedCase
font.pixelSize: 12
}
}
Then added main.py
:
#!/bin/python3
import sys
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import QObject
from PyQt5.QtGui import QGuiApplication, QSyntaxHighlighter, QTextDocument
from PyQt5.QtWidgets import QTextEdit
from PyQt5.QtQml import QQmlApplicationEngine, qmlRegisterType
class Highlighter(QSyntaxHighlighter):
def __init__(self, parent=None):
super(Highlighter, self).__init__(parent)
keywordFormat = QtGui.QTextCharFormat()
keywordFormat.setForeground(QtCore.Qt.darkBlue)
keywordFormat.setFontWeight(QtGui.QFont.Bold)
keywordPatterns = ["\\bchar\\b", "\\bclass\\b", "\\bconst\\b",
"\\bdouble\\b", "\\benum\\b", "\\bexplicit\\b", "\\bfriend\\b",
"\\binline\\b", "\\bint\\b", "\\blong\\b", "\\bnamespace\\b",
"\\boperator\\b", "\\bprivate\\b", "\\bprotected\\b",
"\\bpublic\\b", "\\bshort\\b", "\\bsignals\\b", "\\bsigned\\b",
"\\bslots\\b", "\\bstatic\\b", "\\bstruct\\b",
"\\btemplate\\b", "\\btypedef\\b", "\\btypename\\b",
"\\bunion\\b", "\\bunsigned\\b", "\\bvirtual\\b", "\\bvoid\\b",
"\\bvolatile\\b"]
self.highlightingRules = [(QtCore.QRegExp(pattern), keywordFormat)
for pattern in keywordPatterns]
classFormat = QtGui.QTextCharFormat()
classFormat.setFontWeight(QtGui.QFont.Bold)
classFormat.setForeground(QtCore.Qt.darkMagenta)
self.highlightingRules.append((QtCore.QRegExp("\\bQ[A-Za-z]+\\b"),
classFormat))
singleLineCommentFormat = QtGui.QTextCharFormat()
singleLineCommentFormat.setForeground(QtCore.Qt.red)
self.highlightingRules.append((QtCore.QRegExp("//[^\n]*"),
singleLineCommentFormat))
self.multiLineCommentFormat = QtGui.QTextCharFormat()
self.multiLineCommentFormat.setForeground(QtCore.Qt.red)
quotationFormat = QtGui.QTextCharFormat()
quotationFormat.setForeground(QtCore.Qt.darkGreen)
self.highlightingRules.append((QtCore.QRegExp("\".*\""), quotationFormat))
functionFormat = QtGui.QTextCharFormat()
functionFormat.setFontItalic(True)
functionFormat.setForeground(QtCore.Qt.blue)
self.highlightingRules.append((QtCore.QRegExp("\\b[A-Za-z0-9_]+(?=\\()"), functionFormat))
self.commentStartExpression = QtCore.QRegExp("/\\*")
self.commentEndExpression = QtCore.QRegExp("\\*/")
def highlightBlock(self, text):
for pattern, format in self.highlightingRules:
expression = QtCore.QRegExp(pattern)
index = expression.indexIn(text)
while index >= 0:
length = expression.matchedLength()
self.setFormat(index, length, format)
index = expression.indexIn(text, index + length)
self.setCurrentBlockState(0)
startIndex = 0
if self.previousBlockState() != 1:
startIndex = self.commentStartExpression.indexIn(text)
while startIndex >= 0:
endIndex = self.commentEndExpression.indexIn(text, startIndex)
if endIndex == -1:
self.setCurrentBlockState(1)
commentLength = len(text) - startIndex
else:
commentLength = endIndex - startIndex + self.commentEndExpression.matchedLength()
self.setFormat(startIndex, commentLength, self.multiLineCommentFormat)
startIndex = self.commentStartExpression.indexIn(text, startIndex + commentLength)
def print_child(el):
print(" " * 2 * print_child.max + "type: {}".format(type(el)))
print(" " * 2 * print_child.max + "name: {}".format(el.objectName()))
print_child.max += 1
try:
for subel in el.children():
print_child(subel)
except TypeError:
pass
print_child.max -= 1
print_child.max = 0
def child_selector(children, ftype):
for ch in children:
if type(ch) is ftype:
return ch
return None
if __name__ in "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load("main.qml")
print_child(engine.rootObjects()[0])
el = Highlighter(
child_selector(engine.rootObjects()[0].findChild(QObject, "text_view").children(), QTextDocument)
)
engine.quit.connect(app.quit)
sys.exit(app.exec_())
Print child produces this output:
type: <class 'PyQt5.QtGui.QWindow'>
name:
type: <class 'PyQt5.QtCore.QObject'>
name:
type: <class 'PyQt5.QtCore.QObject'>
name: text_view
type: <class 'PyQt5.QtGui.QTextDocument'>
name:
type: <class 'PyQt5.QtGui.QAbstractTextDocumentLayout'>
name:
type: <class 'PyQt5.QtCore.QObject'>
name:
type: <class 'PyQt5.QtGui.QTextFrame'>
name:
type: <class 'PyQt5.QtCore.QObject'>
name:
type: <class 'PyQt5.QtCore.QObject'>
name:
type: <class 'PyQt5.QtGui.QTextDocument'>
name:
type: <class 'PyQt5.QtGui.QAbstractTextDocumentLayout'>
name:
type: <class 'PyQt5.QtCore.QObject'>
name:
type: <class 'PyQt5.QtGui.QTextFrame'>
name:
type: <class 'PyQt5.QtCore.QObject'>
name:
Questions:
I've defined Window with two TextEdits inside, whereas I see that my TextEdits are embedded in additional QObject's ( which is why I had to add child_selector(...)
and couldn't use findChild output directly ) Why is that and could it be done somehow better?
Is there some function same as findChild(...)
but using id not objectName?
Using findChildren(...) on engine
without rootObject()
resulted with None
I can't get why?
Upvotes: 1
Views: 1073
Reputation: 243897
- I've defined Window with two TextEdits inside, whereas I see that my TextEdits are embedded in additional QObject's ( which is why I had to add child_selector(...) and couldn't use findChild output directly ) Why is that and could it be done somehow better
Yes, you can use findChild, in the following part I show you the solution:
findChild has selectors: the type and the objectName, in the case of textDocument, so as a first test it can be filtered by type, I'll use findChildren since every TextEdit has a textDocument:
root = engine.rootObjects()[0]
docs = root.findChildren(QtGui.QTextDocument)
print(docs)
for doc in docs:
el = Highlighter(doc)
Output:
[<PyQt5.QtGui.QTextDocument object at 0x7f5703eb4af8>, <PyQt5.QtGui.QTextDocument object at 0x7f5703eb4b88>]
by applying the filter of the objectname we can filter the textDocument of the first TextEdit:
root = engine.rootObjects()[0]
text_view = root.findChild(QtCore.QObject, "text_view")
doc = text_view.findChild(QtGui.QTextDocument)
el = Highlighter(doc)
- Is there some function same as findChild(...) but using id not objectName?
The id only has a meaning in QML so you can not use it from python, also as findChild() and findChildren() only use the objectname and type.
The id is an identifier that depends on the scope, for example:
MyItem.qml
Item{
id: root
// others code
}
main.qml
Window{
MyItem{
id: it1
}
MyItem{
id: it2
}
}
As you see, the root id will only identify the Item within MyItem.qml, outside of it it has no meaning, conceptually it is like this
in c++ or self
in python, since those properties have a meaning with respect to the scope.
- Using findChildren(...) on engine without rootObject() resulted with None I can't get why?
QmlEngine is a class that allows to create components, it does not have hierarchical relation, for that reason no element of the tree of hierarchies in QML has like father to the engine, therefore it returns None.
I will answer a question made in the comments since the answer deserves it.
QTextDocument inherits from QObject so it has objectName, so as QTextDocument is a property of TextEdit can it objectName be set in QML somehow?
Unfortunately it is not possible to place an objectName in QML to the QTextDocument since in QML the QTextDocument is not accessible. If we review the docs:
textDocument is a property of TextEdit that is a QQuickTextDocument, and that QQuickTextDocument has a method call textDocument() that just returns a QTextDocument but that method is not a Q_INVOKABLE
nor a SLOT
.
Something closer to what you want is to set an objectName to the textDocument and then get the QTextDocument with the textDocument() method (maybe it will be confusing since the names are similar), to set the objectName we will use the Component.OnCompleted signal:
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello")
TextEdit {
id: text_view
width: parent.width
height: parent.height * 0.8
color: "black"
text: qsTr("long class")
font.pixelSize: 12
anchors.margins: 5
Component.onCompleted: text_view.textDocument.objectName = "textDocument1"
}
TextEdit {
id: text_input
anchors.top: text_view.bottom
width: parent.width
height: parent.height - text_view.height
color: "#ef1d1d"
text: qsTr("")
font.capitalization: Font.MixedCase
font.pixelSize: 12
Component.onCompleted: text_input.textDocument.objectName = "textDocument2"
}
}
*.py
# ...
if __name__ in "__main__":
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
engine.load("main.qml")
if not engine.rootObjects():
sys.exit(-1)
root = engine.rootObjects()[0]
q_text_document1 = root.findChild(QtQuick.QQuickTextDocument, "textDocument1")
h1 = Highlighter(q_text_document1.textDocument())
q_text_document2 = root.findChild(QtQuick.QQuickTextDocument, "textDocument2")
h2 = Highlighter(q_text_document2.textDocument())
engine.quit.connect(app.quit)
sys.exit(app.exec_())
Although I personally prefer not to expose the elements created in QML to python/C++ through the objectName since QML handles its life cycle and not python/C++. In this case I prefer to export the class as a new Item, and create a slot to pass the property of the QQuickTextDocument
:
main.py
#!/bin/python3
import sys
from PyQt5 import QtCore, QtGui, QtQml, QtQuick
class Highlighter(QtGui.QSyntaxHighlighter):
def __init__(self, parent=None):
super(Highlighter, self).__init__(parent)
keywordFormat = QtGui.QTextCharFormat()
keywordFormat.setForeground(QtCore.Qt.darkBlue)
keywordFormat.setFontWeight(QtGui.QFont.Bold)
keywordPatterns = ["\\bchar\\b", "\\bclass\\b", "\\bconst\\b",
"\\bdouble\\b", "\\benum\\b", "\\bexplicit\\b", "\\bfriend\\b",
"\\binline\\b", "\\bint\\b", "\\blong\\b", "\\bnamespace\\b",
"\\boperator\\b", "\\bprivate\\b", "\\bprotected\\b",
"\\bpublic\\b", "\\bshort\\b", "\\bsignals\\b", "\\bsigned\\b",
"\\bslots\\b", "\\bstatic\\b", "\\bstruct\\b",
"\\btemplate\\b", "\\btypedef\\b", "\\btypename\\b",
"\\bunion\\b", "\\bunsigned\\b", "\\bvirtual\\b", "\\bvoid\\b",
"\\bvolatile\\b"]
self.highlightingRules = [(QtCore.QRegExp(pattern), keywordFormat)
for pattern in keywordPatterns]
classFormat = QtGui.QTextCharFormat()
classFormat.setFontWeight(QtGui.QFont.Bold)
classFormat.setForeground(QtCore.Qt.darkMagenta)
self.highlightingRules.append((QtCore.QRegExp("\\bQ[A-Za-z]+\\b"),
classFormat))
singleLineCommentFormat = QtGui.QTextCharFormat()
singleLineCommentFormat.setForeground(QtCore.Qt.red)
self.highlightingRules.append((QtCore.QRegExp("//[^\n]*"),
singleLineCommentFormat))
self.multiLineCommentFormat = QtGui.QTextCharFormat()
self.multiLineCommentFormat.setForeground(QtCore.Qt.red)
quotationFormat = QtGui.QTextCharFormat()
quotationFormat.setForeground(QtCore.Qt.darkGreen)
self.highlightingRules.append((QtCore.QRegExp("\".*\""), quotationFormat))
functionFormat = QtGui.QTextCharFormat()
functionFormat.setFontItalic(True)
functionFormat.setForeground(QtCore.Qt.blue)
self.highlightingRules.append((QtCore.QRegExp("\\b[A-Za-z0-9_]+(?=\\()"), functionFormat))
self.commentStartExpression = QtCore.QRegExp("/\\*")
self.commentEndExpression = QtCore.QRegExp("\\*/")
def highlightBlock(self, text):
for pattern, format in self.highlightingRules:
expression = QtCore.QRegExp(pattern)
index = expression.indexIn(text)
while index >= 0:
length = expression.matchedLength()
self.setFormat(index, length, format)
index = expression.indexIn(text, index + length)
self.setCurrentBlockState(0)
startIndex = 0
if self.previousBlockState() != 1:
startIndex = self.commentStartExpression.indexIn(text)
while startIndex >= 0:
endIndex = self.commentEndExpression.indexIn(text, startIndex)
if endIndex == -1:
self.setCurrentBlockState(1)
commentLength = len(text) - startIndex
else:
commentLength = endIndex - startIndex + self.commentEndExpression.matchedLength()
self.setFormat(startIndex, commentLength, self.multiLineCommentFormat)
startIndex = self.commentStartExpression.indexIn(text, startIndex + commentLength)
@QtCore.pyqtSlot(QtQuick.QQuickTextDocument)
def setQQuickTextDocument(self, q):
if isinstance(q, QtQuick.QQuickTextDocument):
self.setDocument(q.textDocument())
if __name__ in "__main__":
app = QtGui.QGuiApplication(sys.argv)
QtQml.qmlRegisterType(Highlighter, "Foo", 1, 0, "Highlighter")
engine = QtQml.QQmlApplicationEngine()
engine.load("main.qml")
if not engine.rootObjects():
sys.exit(-1)
root = engine.rootObjects()[0]
engine.quit.connect(app.quit)
sys.exit(app.exec_())
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import Foo 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello")
TextEdit {
id: text_view
width: parent.width
height: parent.height * 0.8
color: "black"
text: qsTr("long class")
font.pixelSize: 12
anchors.margins: 5
}
Highlighter{
id: h1
Component.onCompleted: h1.setQQuickTextDocument(text_view.textDocument)
}
TextEdit {
id: text_input
anchors.top: text_view.bottom
width: parent.width
height: parent.height - text_view.height
color: "#ef1d1d"
text: qsTr("")
font.capitalization: Font.MixedCase
font.pixelSize: 12
}
Highlighter{
id: h2
Component.onCompleted: h2.setQQuickTextDocument(text_input.textDocument)
}
}
With this last method the life cycle of the objects is handled by QML, with the initial methods QML could eliminate some item and python/C++ would not know it since nobody notifies it.
Upvotes: 1