Drawing QQuickItem's texture in a "draw-chain" through 2 QQuickFramebufferObjects fails - qt

First off, sorry for the long source code but I couldn't reduce it any further while still having the bug happen. Most of it is uninteresting boilerplate though, so you can mostly get away with just my human-readable summary below.
Abbreviations used in this post:
QQFBO = QQuickFramebufferObject
Observed result: Two rotating squares, that's all.
What's missing from the observed result: A third, additional, non-rotating square, which is actually a static snapshot from the second one.
What my code does, in short:
class ClonerItem is a QQFBO just fetches a texture from a QQuickItem and draws it to the screen.
The ClonerItemQml is a Qml wrapper of ClonerItem that adds a Timer to it. Depending on the ClonerItem.live property, the timer either calls ClonerItem.update() constantly or not at all. (basically the same the live property of Qt's ShaderEffectSource)
In main.qml I create a "producer-consumer-chain" of 3 items in a RowLayout.
The first item is a rotating rectangle with layer.enabled: true.
The second item is a ClonerItemQml (with live: true) that has the previous one as a texture source.
The third item is a ClonerItemQml (called "snapshotter", with live: false) that has the the previous one as a texture source.
"snapshotter" has its update method connected to 2 signals:
A firstRedraw signal provided by item "2", which is called at the end of the first redraw of item "2"
For testing, the clicked signal for "snapshotter", so you can see that the "snapshotter" does work correctly when you force it to update later during the program's execution.
Any hints what I'm doing wrong?
Note: When I remove the Timer and instead of it, uncomment the if(m_live) update(); in render, it works fine.
My code:
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickFramebufferObject>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include <QOpenGLFramebufferObject>
#include <QQuickWindow>
// propertyhelper.h is from http://syncor.blogspot.bg/2014/11/qt-auto-property.html
#include "propertyhelper.h"
#include <QSGTextureProvider>
class ClonerItem : public QQuickFramebufferObject {
Q_OBJECT
AUTO_PROPERTY(QQuickItem*, sourceItem)
AUTO_PROPERTY(bool, live)
public:
Renderer* createRenderer() const;
signals:
void firstRedraw();
};
class ClonerItemRenderer : public QObject, public QQuickFramebufferObject::Renderer, protected QOpenGLFunctions {
Q_OBJECT
public:
ClonerItemRenderer() {
initializeOpenGLFunctions();
m_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shader.vert.glsl");
m_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shader.frag.glsl");
m_program.link();
createGeometry();
}
void render() {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
m_program.bind();
glActiveTexture(GL_TEXTURE0);
m_tex->bind();
m_program.setUniformValue("uTex", 0);
paintGeometry();
glFlush();
glFinish();
m_window->resetOpenGLState();
if(!m_haveRedrawnAtLeastOnce)
emit firstRedraw();
m_haveRedrawnAtLeastOnce = true;
//if(m_live)
// update();
}
void paintGeometry() {
m_program.enableAttributeArray("aPos");
m_program.setAttributeArray("aPos", m_vertices.constData());
glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
}
void createGeometry() {
m_vertices << QVector2D(-1, -1) << QVector2D(-1, +1) << QVector2D(+1, +1);
m_vertices << QVector2D(-1, -1) << QVector2D(+1, -1) << QVector2D(+1, +1);
}
QOpenGLFramebufferObject* createFramebufferObject(const QSize &size) {
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
return new QOpenGLFramebufferObject(size, format);
}
private:
QVector<QVector2D> m_vertices;
QOpenGLShaderProgram m_program;
QQuickWindow* m_window;
QSGTexture* m_tex;
bool m_haveRedrawnAtLeastOnce = false;
bool m_live;
protected:
void synchronize(QQuickFramebufferObject* qqfbo) {
ClonerItem* parentItem = (ClonerItem*)qqfbo;
m_window = parentItem->window();
m_live = parentItem->live();
// fetch texture pointer
QQuickItem* sourceItem = parentItem->sourceItem();
QSGTextureProvider* sourceTexProvider = sourceItem->textureProvider();
m_tex = sourceTexProvider->texture();
}
signals:
void firstRedraw();
};
QQuickFramebufferObject::Renderer* ClonerItem::createRenderer() const {
auto renderer = new ClonerItemRenderer();
connect(renderer, &ClonerItemRenderer::firstRedraw, this, &ClonerItem::firstRedraw);
return renderer;
}
int main(int argc, char **argv) {
QGuiApplication app(argc, argv);
qmlRegisterType<ClonerItem>("ClonerItem", 1, 0, "ClonerItem");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
ClonerItem.qml:
import QtQuick 2.0
import ClonerItem 1.0
ClonerItem {
id: root
Timer {
id: renderTimer
running: root.live
interval: 1000/60
repeat: true
onTriggered: {
root.update();
}
}
}
main.qml:
import QtQuick 2.0
import ClonerItem 1.0
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
Window {
visible: true
width: 600
height: 200
RowLayout {
id: rowLayout
anchors.fill: parent
Item {
id: original
layer.enabled: true
Layout.fillWidth: true
Layout.fillHeight: true
RotatingSquare { }
}
ClonerItemQml {
id: liveClone
layer.enabled: true
sourceItem: original
live: true
Layout.fillWidth: true
Layout.fillHeight: true
onFirstRedraw: {
snapshotter.update();
}
}
ClonerItemQml {
id: snapshotter
sourceItem: liveClone
live: false
Layout.fillWidth: true
Layout.fillHeight: true
MouseArea {
anchors.fill: parent
onClicked: {
snapshotter.update();
}
}
}
}
}
RotatingSquare.qml:
import QtQuick 2.0
Rectangle {
color: "red"
border.color: "black"
width: parent.width / 2
height: parent.height / 2
anchors.centerIn: parent
NumberAnimation on rotation {
running: true
loops: Animation.Infinite
duration: 1000
from: 0
to: 360
}
}
shader.frag.glsl:
varying highp vec2 vTexCoord;
uniform sampler2D uTex;
void main() {
gl_FragColor = texture2D(uTex, vTexCoord);
}
shader.vert.glsl
attribute highp vec2 aPos;
varying highp vec2 vTexCoord;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
// aPos's components are in [-1, 1], transform them to the range [0, 1] for use as texcoords.
vTexCoord = aPos * .5 + vec2(.5);
}
If you're wondering why I'm doing such an elaborate/weird setup, I can elucidate: in my real app the second item in the "chain" is not a simple "cloner" but actually uses its source texture(s) for some more-complex rendering (which is also memory-hungry). And the third item in the "chain" is necessary to snapshot the result of the second one, so the second one can be destroyed when not animated, freeing up memory.
The reason I implement my own ClonerItem component, which does basically the same as Qt's ShaderEffectSource, is this Qt bug.

Related

Qt QML How to manually select word in TextArea by double click

double click selects words, but i'd like to customise it to, for example, select the $1000 term.
Example:
import QtQuick 2.7
import QtQuick.Controls 1.4
ApplicationWindow
{
visible: true
width: 640
height: 480
TextArea
{
anchors.fill: parent
text: "hello, try to select the $1000 prize!"
font.pointSize: 14
}
}
For example, by somehow overriding selectWord or by overriding onDoubleClicked or by somehow adding my own MouseArea that doesn't break the existing TextArea functionality.
Not sure how to do this.
Thanks for any help.
update
I tried adding a MouseArea but it didnt work. Example;
ApplicationWindow
{
visible: true
width: 640
height: 480
TextArea
{
anchors.fill: parent
text: "hello, try to select the $1000 prize!"
font.pointSize: 14
MouseArea
{
anchors.fill: parent
propagateComposedEvents: true
onClicked:
{
// attempt to get an event on click without
// affecting the TextArea. But it breaks selection.
console.log("clicked some text")
mouse.accepted = false
}
}
}
}
Update 2
I think this problem is a version of the long-running Qt problem that you can't have some kind of "event observer" whose job is to check events but not stop them from continuing their normal operation.
If i had an event observer, i could make it "observe" the TextArea and do something on click or double click.
So, here goes trying to make one....
toucharea.h
#pragma once
#include <QGuiApplication>
#include <QQuickItem>
#include <QTime>
class TouchArea : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged)
public:
QTime _lastMousePress;
int _clickThresholdMS = 300;
bool eventFilter(QObject*, QEvent *event) override
{
// if false this will allow the event to continue as normal
// if true it will stop the event propagating
bool handled = false;
// https://doc.qt.io/qt-5/qevent.html#Type-enum
QEvent::Type t = event->type();
switch (t)
{
case QEvent::TouchUpdate:
break;
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug("key press %d", keyEvent->key());
}
break;
case QEvent::MouseButtonPress:
{
qDebug() << "mouse press";
_lastMousePress.start();
break;
}
case QEvent::MouseButtonRelease:
{
qDebug() << "mouse release";
int dt = _lastMousePress.elapsed();
if (dt < _clickThresholdMS)
{
qDebug() << "mouse click";
emit clicked();
}
break;
}
}
return handled;
}
QQuickItem *target() const { return _target; }
void setTarget(QQuickItem *target)
{
qDebug() << "set target";
if (_target == target) return;
if (_target)
_target->removeEventFilter(this);
_target = target;
if (_target)
_target->installEventFilter(this);
emit targetChanged();
}
signals:
void targetChanged();
void clicked();
private:
QQuickItem* _target = 0;
};
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <qqmlcontext.h>
#include "toucharea.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<TouchArea>("App", 1, 0, "TouchArea");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Controls 1.4
import App 1.0
ApplicationWindow
{
visible: true
width: 640
height: 480
TextArea
{
id: tarea
anchors.fill: parent
text: "hello, try to select the $1000 prize!"
font.pointSize: 14
MouseArea
{
enabled: false
anchors.fill: parent
TouchArea
{
target: parent
onClicked: console.log("captured a click")
}
}
}
}
Well it nearly worked. I can only capture my synthetic click if we handled the event in eventFilter. When not handled the filter does not see the MouseButtonRelease.
Why is this. I was expecting this to be the first handler encountered before the other QtQuick items get to see it.
Any help?
Figured out a way;
Step 1, define an Observer class for events
observer.h
#pragma once
#include <QGuiApplication>
#include <QQuickItem>
#include <QTime>
#include <QMouseEvent>
class Observer : public QQuickItem
{
Q_OBJECT
public:
QTime _lastMousePress;
int _clickThresholdMS = 300;
Observer()
{
setFiltersChildMouseEvents(true);
}
bool childMouseEventFilter(QQuickItem*, QEvent *event) override
{
// if false this will allow the event to continue as normal
// if true it will stop the event propagating
bool handled = false;
// https://doc.qt.io/qt-5/qevent.html#Type-enum
QEvent::Type t = event->type();
switch (t)
{
case QEvent::TouchUpdate:
break;
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug("key press %d", keyEvent->key());
}
break;
case QEvent::MouseButtonPress:
{
//qDebug() << "mouse press";
_lastMousePress.start();
}
break;
case QEvent::MouseButtonRelease:
{
//qDebug() << "mouse release";
int dt = _lastMousePress.elapsed();
if (dt < _clickThresholdMS)
{
//qDebug() << "mouse click";
emit clicked();
}
}
break;
case QEvent::MouseButtonDblClick:
{
//QMouseEvent* mevent = static_cast<QMouseEvent*>(event);
//qDebug() << "mouse double click";
emit doubleClicked();
handled = true;
}
break;
}
return handled;
}
signals:
void clicked();
void doubleClicked();
};
Step 2, put this in main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <qqmlcontext.h>
#include "observer.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Observer>("App", 1, 0, "Observer");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
Step 3, use Observer to detect event
Detect whichever event you want, then make it do what you need, for example, for double click to select a wider class of characters in TextArea;
import QtQuick 2.12
import QtQuick.Controls 1.4
import App 1.0
ApplicationWindow
{
visible: true
width: 640
height: 480
Observer
{
anchors.fill: parent
onDoubleClicked:
{
tarea.selectWord();
var s = tarea.selectionStart
var e = tarea.selectionEnd
function allowed(c)
{
if (c == "$" || c == "#") return true;
if (c >= "0" && c <= "9") return true;
if (c.toUpperCase() != c.toLowerCase()) return true;
return false;
}
while (allowed(tarea.getText(s-1, s))) tarea.select(--s, e);
while (allowed(tarea.getText(e, e+1))) tarea.select(s, ++e);
}
TextArea
{
id: tarea
anchors.fill: parent
text: "hello, try to select the #$$$1000###$foo prize!"
font.pointSize: 14
}
}
}
Looks like you can do this with TapHandler:
import QtQuick 2.15
import QtQuick.Controls 2.15
TextField {
width: parent.width
selectByMouse: true
TapHandler {
grabPermissions: PointerHandler.TakeOverForbidden
onDoubleTapped: (eventPoint) => {
print("taptap");
eventPoint.accepted = true;
}
}
}
Note that I experienced both that the double-click = select string on TextField did and did not work after the above. Click+drag to select always worked though.
I cannot think of any solution that would preserve the TextArea behavior, just on the QML side. I would instead try to wrap, inherit or reimplement the component on the C++ side, to add what you want.
Inheriting may be tricky, as few Qt classes are meant to be inherited directly, looking at QQuickTextArea and QQuickTextEdit source, it might be challenging
Reimplementing can be some work, but that would probably be the option I would go for, while keeping the logic and code there to a minimum. QQuickTextArea seems a good base/reference as smaller/simpler than QQuickTextArea (which in turn uses TextArea internally)
Two key elements to that:
Events management (mouse, selection, ...)
Rendering of the component, based on Qt OpenGL wrapper classes (QSG* Scene graph classes). It is a bit obscure, but there are some tutorials and presentations out there about it. And you may be able to just copy-paste the original code.
For both, I would recommend digging into the source code:
QQuickTextEdit: https://code.qt.io/cgit/qt/qtdeclarative.git/tree/src/quick/items/qquicktextedit.cpp
QQuickTextArea: https://code.qt.io/cgit/qt/qtquickcontrols2.git/tree/src/quicktemplates2/qquicktextarea.cpp

how to update a QPaintedTextureImage subclass from QML - Property 'update' of object QPaintedTextureImage is not a function

I want to display a stream of images from a capture device in Qt3D (via a texture).
So I subclassed QPaintedTextureImage:
#include <Qt3DRender/QPaintedTextureImage>
#include <QPainter>
#include <QImage>
#include <QPaintDevice>
#include <opencv2/opencv.hpp>
class OpenCVCaptureImage : public Qt3DRender::QPaintedTextureImage
{
public:
explicit OpenCVCaptureImage(Qt3DCore::QNode *parent = nullptr);
void paint(QPainter *painter) override;
private:
cv::VideoCapture cap_;
};
OpenCVCaptureImage::OpenCVCaptureImage(Qt3DCore::QNode *parent)
: Qt3DRender::QPaintedTextureImage(parent),
cap_(0)
{
}
void OpenCVCaptureImage::paint(QPainter *painter)
{
cv::Mat frame;
cap_ >> frame;
QImage im(static_cast<uchar *>(frame.data), frame.cols, frame.rows, int(frame.step), QImage::Format_RGB888);
if(im.isNull()) return;
setSize(im.size());
//int w = painter->device()->width();
//int h = painter->device()->height();
painter->drawImage(0, 0, im);
}
According to documentation:
A QPaintedTextureImage must be subclassed and the virtual paint() function implemented. Each time update() is called on the QPaintedTextureImage, the paint() function is invoked and the resulting image is uploaded.
So I add a Timer to my scene tree, and in the onTriggered slot I call capImage.update(), but something goes wrong at runtime:
2019-01-31 22:48:54.865282+0100 Qt3DOpenCV[50001:4569220] qrc:/MyScene.qml:57: TypeError: Property 'update' of object Qt3DRender::QPaintedTextureImage(0x106ef6bf0) is not a function
main.cpp:
#include <QGuiApplication>
#include <Qt3DQuickExtras/qt3dquickwindow.h>
#include "opencvcaptureimage.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
qmlRegisterType<OpenCVCaptureImage>("OpenCVCaptureImage", 1, 0, "OpenCVCaptureImage");
QGuiApplication app(argc, argv);
Qt3DExtras::Quick::Qt3DQuickWindow view;
view.setSource(QUrl("qrc:/MyScene.qml"));
view.show();
return app.exec();
}
MyScene.qml:
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
import Qt3D.Extras 2.0
import OpenCVCaptureImage 1.0
import QtQml 2.12
Entity {
id: sceneRoot
RenderSettings {
id: renderSettings
activeFrameGraph: ForwardRenderer {
clearColor: Qt.rgba(0, 0.5, 1, 1)
camera: Camera {
id: objectsCamera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
aspectRatio: 16/9
nearPlane : 0.1
farPlane : 1000.0
position: Qt.vector3d( 0.0, 0.0, -40.0 )
upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 )
}
}
}
InputSettings {
id: inputSettings
}
components: [renderSettings, inputSettings]
PlaneMesh {
id: planeMesh
width: 2
height: 2
}
TextureMaterial {
id: planeMaterial
texture: Texture2D {
id: planeTexture
textureImages: [
OpenCVCaptureImage {
id: capImage
}
]
}
}
Timer {
interval: 500
running: true
repeat: true
onTriggered: capImage.update()
}
Transform {
id: planeTransform
rotationX: 90
}
Entity {
id: planeEntity
components: [ planeMesh, planeMaterial, planeTransform ]
}
}
EDIT: I also tried putting the timer in the C++ constructor:
OpenCVCaptureImage::OpenCVCaptureImage(Qt3DCore::QNode *parent)
: Qt3DRender::QPaintedTextureImage(parent),
cap_(0)
{
QTimer::singleShot(2500, [=] {
auto t = new QTimer(this);
connect(t, &QTimer::timeout, [=] {
this->update();
});
t->setInterval(200);
t->start();
});
}
in that case the program crashes in QPainter::drawImage, and this error is printed to console:
2019-02-01 11:15:45.754907+0100 Qt3DOpenCV[55788:4839633] QPaintDevice: Cannot destroy paint device that is being painted
I'm not entirely sure what you are doing wrong, but I used QPaintedTextureImage in this example successfully and added a timer that updates the texture periodically. Qt3D doesn't call it directly but you can like you suggested in your question. Maybe try cleaning the project, then run qmake and build it afterwards.

Save QPainter after called update

I want to draw straight lines on qml , but every time I draw a line, the previous disappears, I wonder if it has any thing to do with the update method, any ways to solve this problem.
main.qml
MouseArea{
id:fidpoint
anchors.fill: parent
onPressed: {
switch(addstate.currentText){
case 'Track':
map.setTstart(mouseX,mouseY);
draw_line.setColor("black");
draw_line.setStart(Qt.point(mouseX,mouseY));
draw_line.setEnd(Qt.point(mouseX,mouseY));
draw_line.update();
break;
}
}
onReleased: {
switch(addstate.currentText){
case 'Track':
map.addTrack(mouseX,mouseY);
draw_line.setColor("black");
draw_line.setEnd(Qt.point(mouseX,mouseY));
draw_line.update();
break;
}
}
onPositionChanged: {
switch(addstate.currentText){
case 'Track':
draw_line.setColor("black");
draw_line.setEnd(Qt.point(mouseX,mouseY));
draw_line.update();
break;
}
}
}
draw_line is the Object's id that I registered to qml from main.cpp
paint.cpp
void Paint::paint(QPainter *painter)
{
QPen pen(m_color, 2);
painter->setPen(pen);
painter->setRenderHint(QPainter::Antialiasing,true);
painter->drawLine(startNode,endNode);
}
paint is the class that inherits from QQuickPaintedItem
A possible option would be to create an image and save the changes there, but this would solve your problem in the short term. A better option is that draw_line draw only one line, then add it as a child of the item you want the canvas to be.
lineitem.h
#ifndef LINEITEM_H
#define LINEITEM_H
#include <QQuickPaintedItem>
class LineItem: public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QPoint startPos READ startPos WRITE setStartPos NOTIFY startPosChanged)
Q_PROPERTY(QPoint endPos READ endPos WRITE setEndPos NOTIFY endPosChanged)
Q_PROPERTY(QColor lineColor READ lineColor WRITE setLineColor NOTIFY lineColorChanged)
public:
using QQuickPaintedItem::QQuickPaintedItem;
void paint(QPainter *painter);
QPoint startPos() const;
void setStartPos(const QPoint &startPos);
QPoint endPos() const;
void setEndPos(const QPoint &endPos);
QColor lineColor() const;
void setLineColor(const QColor &lineColor);
signals:
void startPosChanged();
void endPosChanged();
void lineColorChanged();
private:
QPoint m_startPos;
QPoint m_endPos;
QColor m_lineColor;
};
#endif // LINEITEM_H
lineitem.cpp
#include "lineitem.h"
#include <QPainter>
void LineItem::paint(QPainter *painter)
{
painter->setRenderHint(QPainter::Antialiasing, true);
QPen pen(m_lineColor, 2);
painter->setPen(pen);
painter->drawLine(m_startPos, m_endPos);
}
QPoint LineItem::startPos() const
{
return m_startPos;
}
void LineItem::setStartPos(const QPoint &startPos)
{
if(m_startPos == startPos)
return;
m_startPos = startPos;
emit startPosChanged();
update();
}
QPoint LineItem::endPos() const
{
return m_endPos;
}
void LineItem::setEndPos(const QPoint &endPos)
{
if(m_endPos == endPos)
return;
m_endPos = endPos;
emit endPosChanged();
update();
}
QColor LineItem::lineColor() const
{
return m_lineColor;
}
void LineItem::setLineColor(const QColor &lineColor)
{
if(m_lineColor == lineColor)
return;
m_lineColor = lineColor;
emit lineColorChanged();
update();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import com.eyllanesc.org 1.0
Window {
id: win
visible: true
width: 640
height: 480
title: qsTr("Hello World")
property LineItem currentItem: null
Rectangle{
id: canvas
anchors.fill: parent
MouseArea{
anchors.fill: parent
onPressed: {
currentItem = create_lineitem(canvas)
currentItem.lineColor = "green"
currentItem.anchors.fill = canvas
currentItem.startPos = Qt.point(mouseX,mouseY)
currentItem.endPos = Qt.point(mouseX,mouseY)
}
onReleased: currentItem.endPos = Qt.point(mouseX,mouseY)
onPositionChanged: currentItem.endPos = Qt.point(mouseX,mouseY)
}
}
function create_lineitem(parentItem, color) {
return Qt.createQmlObject('import com.eyllanesc.org 1.0; LineItem{}',
parentItem);
}
}
main.cpp
#include "lineitem.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<LineItem>("com.eyllanesc.org", 1, 0, "LineItem");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
The complete example can be found in the following link.

QSyntaxHighlighter rehighlight() ignores changes triggered from another thread

I have a spellchecking thread which fires spellcheck() signals from time to time which are connected to my highlighter's rehighlight() method. The latter sets the whole block to have red foreground.
This used to work in Qt 5.6.2 and ceased to work in newer versions. I hopelessly waited for it to get fixed in Qt 5.9.5, but it still does not work (neither in Windows 10, nor in OS X).
Small example which reproduces the problem could be obtained from here https://bitbucket.org/ribtoks/qt-highlighting-issue (in order to repro, type something in the input. rehighlight() will be triggered each 7 seconds from background thread)
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickTextDocument>
#include <QSyntaxHighlighter>
#include <QThread>
#include <QString>
class SpellCheckWorker : public QObject
{
Q_OBJECT
public:
explicit SpellCheckWorker(QObject *parent = 0) : QObject(parent), m_Counter(0), m_IsOK(false)
{ }
public:
bool isOK() { return m_IsOK; }
signals:
void spellcheck();
public slots:
void process() {
qInfo() << "Worker Thread is" << QThread::currentThreadId();
while (1) {
m_Counter++;
m_IsOK = m_Counter % 7 == 0;
QThread::sleep(1);
emit spellcheck();
}
}
private:
int m_Counter;
volatile bool m_IsOK;
};
class SpellCheckErrorsHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
SpellCheckErrorsHighlighter(SpellCheckWorker *worker, QTextDocument *document):
QSyntaxHighlighter(document),
m_Worker(worker)
{ }
virtual ~SpellCheckErrorsHighlighter() {}
protected:
virtual void highlightBlock(const QString &text) override {
if (!m_Worker->isOK()) {
qDebug() << "Worker is not OK" << text;
return;
}
qInfo() << "Reapplied formatting for" << text;
qInfo() << "Highlight thread is" << QThread::currentThreadId();
this->setFormat(0, text.length(), QColor(0xff, 0, 0));
}
private:
SpellCheckWorker *m_Worker;
};
class MainModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
explicit MainModel(QObject *parent = 0) : QObject(parent), m_Worker(nullptr)
{ }
public:
QString name() const { return m_name; }
void startChecking() {
qInfo() << "Main thread is" << QThread::currentThreadId();
m_Worker = new SpellCheckWorker();
QThread *thread = new QThread();
m_Worker->moveToThread(thread);
QObject::connect(thread, &QThread::started, m_Worker, &SpellCheckWorker::process);
thread->start();
}
Q_INVOKABLE void initNameHighlighting(QQuickTextDocument *document) {
SpellCheckErrorsHighlighter *highlighter = new SpellCheckErrorsHighlighter(m_Worker, document->textDocument());
QObject::connect(m_Worker, &SpellCheckWorker::spellcheck,
highlighter, &SpellCheckErrorsHighlighter::rehighlight);
}
signals:
void nameChanged();
public slots:
void setName(QString name)
{
if (m_name == name)
return;
m_name = name;
emit nameChanged();
}
private:
SpellCheckWorker *m_Worker;
QString m_name;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MainModel mainModel;
mainModel.startChecking();
QQmlApplicationEngine engine;
QQmlContext *rootContext = engine.rootContext();
rootContext->setContextProperty("mainModel", &mainModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
and main.qml
import QtQuick 2.6
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.3
import QtQuick.Window 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
StackView {
id: mainStackView
anchors.fill: parent
focus: true
initialItem: Rectangle {
anchors.fill: parent
Rectangle {
id: titleRect
height: 30
width: 300
anchors.centerIn: parent
color: "#ffffff"
border.color: "#000000"
border.width: titleTextInput.activeFocus ? 1 : 0
clip: true
focus: false
Flickable {
id: titleFlick
contentWidth: titleTextInput.paintedWidth
contentHeight: titleTextInput.paintedHeight
height: parent.height
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 5
anchors.rightMargin: 5
clip: true
flickableDirection: Flickable.HorizontalFlick
interactive: false
focus: false
function ensureVisible(r) {
if (contentX >= r.x)
contentX = r.x;
else if (contentX+width <= r.x+r.width)
contentX = r.x+r.width-width;
}
TextEdit {
id: titleTextInput
width: paintedWidth > titleFlick.width ? paintedWidth : titleFlick.width
height: titleFlick.height
text: mainModel.name
focus: true
onTextChanged: mainModel.name = text
Component.onCompleted: mainModel.initNameHighlighting(titleTextInput.textDocument)
onCursorRectangleChanged: titleFlick.ensureVisible(cursorRectangle)
}
}
}
}
}
}
Is there any way to get it working with workarounds? I need to keep spellchecking logic in the background thread so it's not possible to move it to main thread.
You seem to be referring to two different root causes with the following two statements:
There's NO problem in delivering slot call to Highlighter.
and
m_Worker->isOK() check is there for a reason to execute highlighting only once per 7 seconds. The bug in Qt can only be demonstrated with this.
If I just focus on the second statement, it looks like the problem is that you are not able to hit the statement if (!m_Worker->isOK()) right?
That may well be a problem due to your code or a Qt issue on a specific platform.
Can you change the code to avoid the condition i.e. emit the spellcheck signal only when 7 seconds have passed to avoid making this check from another thread?
Ok, so after messing with your code, The issue is that you are not calling this line inside of the hilightBlock directive in main.cpp near line 55 in order to keep the format from before.
Add this in to fix it (I think) The question was still fairly unclear...
if (!m_Worker->isOK()) {
qDebug() << "Worker is not OK" << text;
this->setFormat(0, text.length(), this->format(0));
return;
}
The other part of the issue is that you don't have any rules defined for the syntax highlighter... so it will always be red once it turns red.

Taking texture from Image and drawing it in QQuickFramebufferObject. Not all instances are rendered

What I'm doing, in short:
Deriving a MyItem class from QQuickFramebufferObject
In MyItem I have a QQuickItem* sourceItem property from which I fetch a texture and draw it in a triangle
From QML I supply an Image called sourceItem to MyItem. This Image has cache: false
When the Image finishes loading, I wait one frame and then call update on MyItem
I have a 6x6 grid of MyItem instances
The problem is that some of the instances of MyItem draw nothing:
Any ideas?
My code:
main.cpp:
#include <QQmlApplicationEngine>
#include <QGuiApplication>
#include <QQuickFramebufferObject>
#include <QOpenGLFramebufferObject>
#include <QSGTextureProvider>
#include <QSGTexture>
#include <QQuickWindow>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include "propertyhelper.h" // this file is from http://syncor.blogspot.bg/2014/11/qt-auto-property.html
class MyItem : public QQuickFramebufferObject {
Q_OBJECT
AUTO_PROPERTY(QQuickItem*, sourceItem)
public:
Renderer *createRenderer() const;
};
class MyItemRenderer : public QQuickFramebufferObject::Renderer, protected QOpenGLFunctions {
public:
MyItemRenderer() {
initializeOpenGLFunctions();
m_program.addShaderFromSourceCode(QOpenGLShader::Vertex,
"in highp vec2 aPos;\
out highp vec2 vTexCoord;\
\
void main() {\
gl_Position = vec4(aPos, 0.0, 1.0);\
vTexCoord = aPos * .5 + .5;\
}"
);
m_program.addShaderFromSourceCode(QOpenGLShader::Fragment,
"in highp vec2 vTexCoord;\
out vec4 outputColor;\
uniform sampler2D uTex;\
\
void main() {\
outputColor = texture(uTex, vTexCoord);\
}"
);
m_program.link();
m_program.setUniformValue("uTex", 0);
createGeometry();
}
void synchronize(QQuickFramebufferObject* qqfbo){
auto item = (MyItem*)qqfbo;
m_window = item->window();
m_tex = item->sourceItem()->textureProvider()->texture();
}
QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) {
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
return new QOpenGLFramebufferObject(size, format);
}
void paintGeometry() {
m_program.enableAttributeArray("aPos");
m_program.setAttributeArray("aPos", m_vertices.constData());
glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
m_program.disableAttributeArray("aPos");
}
void createGeometry() {
m_vertices << QVector2D(-1, -1);
m_vertices << QVector2D(1, -1);
m_vertices << QVector2D(-1, 1);
}
void render() {
glDisable(GL_DEPTH_TEST);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
m_program.bind();
glActiveTexture(GL_TEXTURE0);
m_tex->bind();
paintGeometry();
m_window->resetOpenGLState();
}
private:
QQuickWindow* m_window;
QVector<QVector2D> m_vertices;
QSGTexture* m_tex;
QOpenGLShaderProgram m_program;
};
QQuickFramebufferObject::Renderer *MyItem::createRenderer() const {
return new MyItemRenderer();
}
int main(int argc, char** argv) {
qmlRegisterType<MyItem>("MyItem", 1, 0, "MyItem");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
id: window
width: 600
height: 600
Flow {
anchors.fill: parent
Repeater {
model: 36
delegate: MyItemWrapper {
width: 100
height: 100
}
}
}
}
MyItemWrapper.qml:
import QtQuick 2.5
import MyItem 1.0
Item {
Image {
x: -100000 // hide the sourceItem
id: sourceItem
layer.enabled: true
source: "https://images-na.ssl-images-amazon.com/images/M/MV5BMTg2MTMyMzU0M15BMl5BanBnXkFtZTgwOTU3ODk4NTE#._V1_SX300.jpg"
cache: false
function updateCppItemOnce() {
window.afterRendering.disconnect(updateCppItemOnce);
cppItem.update();
}
onStatusChanged: {
if (status == Image.Ready) {
window.afterRendering.connect(updateCppItemOnce);
}
}
}
MyItem {
sourceItem: sourceItem
anchors.fill: parent
id: cppItem
}
}
Notes:
I tried enabling OpenGL logging but got no messages
I've tried the testcase on 3 other PCs, all of them much weaker/slower than this one. The bug doesn't occur on them. So I think the bug happens only on fast PCs - it may be a timing problem.
I now realized that instead of the clunky way I was calling update(), I could just connect to the item->sourceItem()->textureProvider()->textureChanged() signal. Did that and now it works like a charm.
I'd still like to know why my original way fails, though :)
Gunnar Sletta provided the solution as a comment in my bugreport.
I suspect the problem is your use of the 'afterRendering' signal. When running on a threaded render loop this signal will fire when rendering is done and the slot will be called almost right away on the main thread. Depending on where the threaded render loop is when the image completes loading that means you'll have the slot invoked potentially right away. When the scene graph reaches the sync phase, the order in which nodes are processed is arbitrary, so some nodes may be processed before the image and some after. Those processed before the image gets a texture gets null and those processed after gets a valid texture.
It would be better to connect to afterSynchronization signal as this would actually fire first after the synchronization of the Image was guaranteed to have completed.
Indeed, using afterSynchronizing fixed my problem.

Resources