QGLWidget renders text at incorrect depth - qt

I use this to render text in a QGLWidget
QGLWidget::renderText(x, y, z, text, font)
The string is rendered at a depth of ~0.5 (obtained via glReadPixel()).
However, in my case it should be closer to ~0.9.
When I convert the x,y,z coords into screen coords with the current matrices I also find a result of ~0.9.
Why such a difference? It makes the text appear always in front.
I created a simple QT project in visual studio to reproduce the issue.
It draws a green square with text in front and behind the square. Both text look in front of the square. And the depth of the pixel can be read by hovering the mouse.
I use Qt version 5.5 built for 64bit platform.
MyGLWidget.h
#include <QGLWidget>
#include <QMouseEvent>
class MyGLWidget : public QGLWidget
{
Q_OBJECT
private:
float _depth;
public:
MyGLWidget(QWidget * parent = 0);
virtual ~MyGLWidget();
virtual void initializeGL();
virtual void paintGL();
void mouseMoveEvent(QMouseEvent * event);
signals:
void depthRead(float);
};
MyGLWidget.cpp
#include "MyGLWidget.h"
#include <gl/GLU.h>
#include <Qfont>
MyGLWidget::MyGLWidget(QWidget * parent) : QGLWidget(parent)
{
}
MyGLWidget::~MyGLWidget()
{
}
void MyGLWidget::initializeGL()
{
}
void MyGLWidget::paintGL()
{
// set up projection
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
float width = this->width();
float height = this->height();
glViewport(0, 0, width, height);
gluPerspective(45, width / height, 1, 100);
// set up model view
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0,0,5, // eye
0,0,0, // look at
0,1,0); // up
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
// draw a green square
glColor4f(0,1,0,1);
glBegin(GL_QUADS);
glVertex3f(-1,-1,0);
glVertex3f(1,-1,0);
glVertex3f(1,1,0);
glVertex3f(-1,1,0);
glEnd();
// render some blue text behind the square
QFont font;
font.setPointSize(20);
glColor4f(0,0,1,1);
renderText(-2,-0.5,-1, "BEHIND_BEHIND_BEHIND_BEHIND", font);
// render some red text in front of the square
glColor4f(1,0,0,1);
renderText(-2,0.5,+1, "IN_FRONT_IN_FRONT_IN_FRONT_I", font);
}
void MyGLWidget::mouseMoveEvent(QMouseEvent * event)
{
int x = event->x();
// flip y for QT origin is top left while OpenGL origin is bottom left
int y = this->height() - event->y();
// read pixel depth
glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &_depth);
// update ui
emit depthRead(_depth);
}
rendertexttest.h
#ifndef RENDERTEXTTEST_H
#define RENDERTEXTTEST_H
#include <QtWidgets/QMainWindow>
#include "ui_rendertexttest.h"
#include "MyGLWidget.h"
class RenderTextTest : public QMainWindow
{
Q_OBJECT
public:
RenderTextTest(QWidget *parent = 0);
~RenderTextTest();
public slots:
void onDepthRead(float depth);
private:
Ui::RenderTextTestClass ui;
MyGLWidget * _glwidget;
};
#endif // RENDERTEXTTEST_H
rendertexttest.cpp
#include "rendertexttest.h"
RenderTextTest::RenderTextTest(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
_glwidget = new MyGLWidget(this);
_glwidget->setMouseTracking(true);
QObject::connect(_glwidget, SIGNAL(depthRead(float)),
this, SLOT(onDepthRead(float)));
ui._mainLayout->addWidget(_glwidget);
}
RenderTextTest::~RenderTextTest()
{
}
void RenderTextTest::onDepthRead(float depth)
{
ui._lblDepth->setText(QString::number(depth));
}
ui_rendertexttest.h
/********************************************************************************
** Form generated from reading UI file 'rendertexttest.ui'
**
** Created by: Qt User Interface Compiler version 5.3.1
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_RENDERTEXTTEST_H
#define UI_RENDERTEXTTEST_H
#include <QtCore/QVariant>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_RenderTextTestClass
{
public:
QWidget *centralWidget;
QWidget *verticalLayoutWidget;
QVBoxLayout *_mainLayout;
QLabel *_lblDepth;
void setupUi(QMainWindow *RenderTextTestClass)
{
if (RenderTextTestClass->objectName().isEmpty())
RenderTextTestClass->setObjectName(QStringLiteral("RenderTextTestClass"));
RenderTextTestClass->resize(600, 438);
centralWidget = new QWidget(RenderTextTestClass);
centralWidget->setObjectName(QStringLiteral("centralWidget"));
verticalLayoutWidget = new QWidget(centralWidget);
verticalLayoutWidget->setObjectName(QStringLiteral("verticalLayoutWidget"));
verticalLayoutWidget->setGeometry(QRect(9, 9, 581, 381));
_mainLayout = new QVBoxLayout(verticalLayoutWidget);
_mainLayout->setSpacing(6);
_mainLayout->setContentsMargins(11, 11, 11, 11);
_mainLayout->setObjectName(QStringLiteral("_mainLayout"));
_mainLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
_mainLayout->setContentsMargins(0, 0, 0, 0);
_lblDepth = new QLabel(centralWidget);
_lblDepth->setObjectName(QStringLiteral("_lblDepth"));
_lblDepth->setGeometry(QRect(10, 410, 581, 16));
RenderTextTestClass->setCentralWidget(centralWidget);
retranslateUi(RenderTextTestClass);
QMetaObject::connectSlotsByName(RenderTextTestClass);
} // setupUi
void retranslateUi(QMainWindow *RenderTextTestClass)
{
RenderTextTestClass->setWindowTitle(QApplication::translate("RenderTextTestClass", "RenderTextTest", 0));
_lblDepth->setText(QApplication::translate("RenderTextTestClass", "Depth:", 0));
} // retranslateUi
};
namespace Ui {
class RenderTextTestClass: public Ui_RenderTextTestClass {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_RENDERTEXTTEST_H

There might 3 issues
1- the target depth is computed correctly in QGLWidget::renderText() and passed to the paintEngine via setTranslateZ(). However the vertex shader does not set this value directly in the vertex clipped coordinates. Instead it translate by this value.
2- the sign of translateZ seems incorrect. That would explain why the depth of the text pixels increases when the text comes closer.
3- it seems clipped coordinates [0, 1] are mapped to range [0.5 , 1] Yet I did not see any call to glDepthRange() in Qt's source.
If we change the code of qglslComplexGeometryPositionOnlyVertexShader in qglengineshadersource_p.h with the following, it fixes the problem.
static const char* const qglslComplexGeometryPositionOnlyVertexShader = "\n\
uniform highp mat3 matrix; \n\
uniform highp float translateZ; \n\
attribute highp vec2 vertexCoordsArray; \n\
void setPosition(void) \n\
{ \n\
vec3 v = matrix * vec3(vertexCoordsArray, 1.0); \n\
v.z = (-translateZ - 0.5f) * 2.0f; \n\
gl_Position = vec4(v.xyz, 1.0);\n\
} \n";

I found that a nice workaround is to render the text into a texture. And then display that texture in the scene.
This works nicely if the text is rendered over a solid background color.
QGLWidget does not seem to write to the alpha channel.

Related

Qt5 : Displaying a Qpainter with layouts

I started using Qt5 a little while ago and I don't know how to set the position of my drawing in my window.
I have a Drawing class which is a QWidget and which contains my paintEvent() function and other functions; and a MainWindow class which contains some widgets. So in order to display my Qpainter, I have to include it in my layout, which is the main window layout, but the window doesn't adapt to the Qpainter at all; the dimensions of the buttons, sliders .. adapt so as to organize themselves and occupy all the space of my window, but they totally ignore my Qpainter and it partly disappears if I put at least 2 widgets.
Do you have any solutions to better manage the position of these elements?
main.cpp :
#include <QtGui>
#include <QApplication>
#include "mywidget.h"
int main( int argc, char **argv )
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
mywidget.h :
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QtGui>
#include <QWidget>
#include <QSlider>
#include <QScrollBar>
#include <QApplication>
#include <QGridLayout>
#include <QObject>
#include <QPoint>
#include <QLabel>
#include <QPolygon>
class Drawing : public QWidget
{
Q_OBJECT
public:
Drawing();
void paintEvent(QPaintEvent* e);
public slots:
void slide(int abscisse);
void rotate();
private:
QPoint o;
QPoint a;
QPoint b;
QPoint c;
QPoint d;
};
//--------------------------------------
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow();
private:
QSlider* m_slider1;
QSlider* m_slider2;
QGridLayout* m_layout;
Drawing* m_dessin;
};
#endif // MYWIDGET_H
mywidget.cpp :
#include "mywidget.h"
#include <iostream> //POUR LES TESTS
MainWindow::MainWindow() : QWidget()
{
setGeometry(330, 140, 840, 620);
m_slider1 = new QSlider(Qt::Horizontal, this);
m_slider1->setRange(150, 650);
m_slider1->setSliderPosition(400);
m_slider2 = new QSlider(Qt::Horizontal, this);
m_slider2->setSliderPosition(50);
m_layout = new QGridLayout;
m_layout->addWidget(new QLabel("Translation Horizontale"), 1, 0);
m_layout->addWidget(m_slider1, 2, 0);
m_layout->addWidget(new QLabel("Rotation"), 0, 1);
m_layout->addWidget(m_slider2, 1, 1);
m_dessin = new Drawing;
m_layout->addWidget(m_dessin, 0, 0);
setLayout(m_layout);
QObject::connect(m_slider1, SIGNAL(valueChanged(int)), m_dessin, SLOT(slide(int)));
QObject::connect(m_slider2, SIGNAL(valueChanged(int)), m_dessin, SLOT(rotate()));
}
//--------------------------------------------------------
Drawing::Drawing() : QWidget(), o(400, 150), a(o.x()-50 , o.y()-50), b(o.x()+50 , o.y()-50), c(o.x()+50 , o.y()+50), d(o.x()-50 , o.y()+50) {}
void Drawing::paintEvent(QPaintEvent *e) {
QPolygon poly;
poly << a << b << c << d;
QWidget::paintEvent(e); // effectue le comportement standard
QPainter painter(this); // construire
painter.setPen( QPen(Qt::white, 2) ); // personnaliser
painter.drawPolygon(poly); // dessiner
}
void Drawing::slide(int abscisse) {
if (a == QPoint(o.x()-50 , o.y()-50)) {
o.setX(abscisse);
a.setX(o.x()-50);
b.setX(o.x()+50);
c.setX(o.x()+50);
d.setX(o.x()-50);
}
else {
o.setX(abscisse);
a.setX(o.x());
b.setX(o.x()+75);
c.setX(o.x());
d.setX(o.x()-75);
}
update();
}
void Drawing::rotate() {
if (a == QPoint(o.x()-50 , o.y()-50)) {
a = QPoint(o.x() , o.y()+75);
b = QPoint(o.x()+75 , o.y());
c = QPoint(o.x() , o.y()-75);
d = QPoint(o.x()-75 , o.y());
}
else {
a = QPoint(o.x()-50 , o.y()-50);
b = QPoint(o.x()+50 , o.y()-50);
c = QPoint(o.x()+50 , o.y()+50);
d = QPoint(o.x()-50 , o.y()+50);
}
update();
}
Snapshots:
After having seen the snapshots of OP, I thought about what might been happen.
The layout of OP doesn't look that wrong.
I still believe that the layout plays only a minor role in OPs issue.
I tried to reproduce OPs issue with an even smaller MCVE of mine.
My testQGridLayout:
#include <QtWidgets>
class Drawing: public QFrame {
public:
Drawing(QWidget *pQParent = nullptr);
virtual ~Drawing() = default;
Drawing(const Drawing&) = delete;
Drawing& operator=(const Drawing&) = delete;
protected:
virtual void paintEvent(QPaintEvent *pQEvent) override;
};
Drawing::Drawing(QWidget* pQParent):
QFrame(pQParent)
{
setFrameStyle(Box | Plain);
}
void Drawing::paintEvent(QPaintEvent* pQEvent)
{
{ QPainter qPainter(this);
qPainter.drawText(QPoint(40, 40),
QString("Size: %1 x %2").arg(width()).arg(height()));
qPainter.setPen(Qt::red);
qPainter.drawRect(300, 100, 200, 200);
}
// call base class paint event to keep it working
QFrame::paintEvent(pQEvent);
}
class MainWindow: public QWidget {
public:
MainWindow(QWidget *pQParent = nullptr);
virtual ~MainWindow() = default;
MainWindow(const MainWindow&) = delete;
MainWindow& operator=(const MainWindow&) = delete;
private:
QGridLayout _qGrid;
Drawing _qDrawing;
QSlider _qSliderT;
QSlider _qSliderR;
};
MainWindow::MainWindow(QWidget *pQParent):
QWidget(pQParent),
_qSliderT(Qt::Horizontal),
_qSliderR(Qt::Horizontal)
{
resize(840, 620);
_qGrid.addWidget(&_qDrawing, 0, 0);
_qGrid.addWidget(new QLabel("Translation Horizontal"), 1, 0);
_qSliderT.setRange(150, 650);
_qSliderT.setSliderPosition(400);
_qGrid.addWidget(&_qSliderT, 2, 0);
_qGrid.addWidget(new QLabel("Rotation"), 1, 1);
_qSliderR.setSliderPosition(50);
_qGrid.addWidget(&_qSliderR, 2, 1);
setLayout(&_qGrid);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
MainWindow qWinMain;
qWinMain.setWindowTitle("Test QGridLayout");
qWinMain.show();
// runtime loop
return app.exec();
}
Output:
Qt Version: 5.15.1
I made some changes to exclude what is a possible issue and what not.
I derived my Drawing from QFrame. Thus, it was easy to give it a visible border. As expected, my Drawing _qDrawing occupies only the space above the first slider (QSlider _qSliderT; in my case).
I added output of widget size to the Drawing::paintEvent() to see its size. Then I added the painting of a red rectangle. For that, I cared to cover a space which is partly inside the widget and partly below and right of it.
This is what I conclude:
As exposed in the OPs code, the layout should be the same.
OPs rectangle is always drawn at the same coordinates. Hence, it doesn't get visible until the Drawing grows large enough (with the main window).
The origin of the QPainter (i.e. QPoint(0, 0)) is the upper left corner of the widget. This can be changed by applying transformations but I couldn't see this in OPs code. (The effect of the sliders, I neglect for now.)
Though, there are still some things which are not clear to me:
The Drawing should clip the painting. Hence, I wonder, how OPs rectangle can appear over the rotate slider. Either, the OP used a span for the Drawing m_dessin, or the widget doesn't clip painting on the paint engine the OP uses. (The look is quite different than mine. Thus, it might be a different platform.)
The layout which can be seen in OPs snapshots doesn't match the exposed code. In OPs snapshot, the Drawing occupies all extra space resulting from growing the main window. This is only possible when QGridLayout::setRowStretch()/GridLayout::setColumnStretch() had been used (as recommended in my first comment). However, the exposed code doesn't contain them.
To check this out, I changed the layout in MainWindow::MainWindow():
MainWindow::MainWindow(QWidget *pQParent):
QWidget(pQParent),
_qSliderT(Qt::Horizontal),
_qSliderR(Qt::Horizontal)
{
resize(840, 620);
_qGrid.setRowStretch(0, 1);
_qGrid.setColumnStretch(0, 1);
_qGrid.addWidget(&_qDrawing, 0, 0, 1, 2);
_qGrid.addWidget(new QLabel("Translation Horizontal"), 1, 0);
_qSliderT.setRange(150, 650);
_qSliderT.setSliderPosition(400);
_qGrid.addWidget(&_qSliderT, 2, 0);
_qGrid.addWidget(new QLabel("Rotation"), 1, 1);
_qSliderR.setSliderPosition(50);
_qGrid.addWidget(&_qSliderR, 2, 1);
setLayout(&_qGrid);
}
Output:
Now, the layout seems to match the one of OPs snapshots.
Trying resize:
This looks exactly as it should:
the Drawing _qDrawing shrinks and grows with the main window size
the painting is clipped if the size of Drawing _qDrawing becomes too small to cover it.
Final Conclusion:
There is nothing wrong in OPs layout.
IMHO, OP is not yet fully clear about how coordinate systems apply in QPainter.
For this, I can warmly recommend an extra page of the Qt online doc., precisely dedicated to this topic:
Qt Doc.: Coordinate System
Continuation:
How to add a vertical slider:
class MainWindow: public QWidget {
public:
MainWindow(QWidget *pQParent = nullptr);
virtual ~MainWindow() = default;
MainWindow(const MainWindow&) = delete;
MainWindow& operator=(const MainWindow&) = delete;
private:
QGridLayout _qGrid;
Drawing _qDrawing;
QSlider _qSliderV;
QSlider _qSliderT;
QSlider _qSliderR;
};
MainWindow::MainWindow(QWidget *pQParent):
QWidget(pQParent),
_qSliderV(Qt::Vertical),
_qSliderT(Qt::Horizontal),
_qSliderR(Qt::Horizontal)
{
resize(840, 620);
_qGrid.setRowStretch(0, 1);
_qGrid.setColumnStretch(0, 1);
_qGrid.addWidget(&_qDrawing, 0, 0, 1, 2);
_qGrid.addWidget(&_qSliderV, 0, 2);
_qGrid.addWidget(new QLabel("Translation Horizontal"), 1, 0);
_qSliderT.setRange(150, 650);
_qSliderT.setSliderPosition(400);
_qGrid.addWidget(&_qSliderT, 2, 0);
_qGrid.addWidget(new QLabel("Rotation"), 1, 1, 1, 2);
_qSliderR.setSliderPosition(50);
_qGrid.addWidget(&_qSliderR, 2, 1, 1, 2);
setLayout(&_qGrid);
}
Output:
To achieve this specific layout, I placed the _qSliderV into column 2 and gave _qSliderR (and its label) a column span of 2 as well.
To illustrate this, I added a sketch of the resulting grid to the above snapshot:
Which behavior would you like for your Drawing widget?
By default it will be resized freely by the layout, you can change this by using QWidget::sizeHint() and QWidget::sizePolicy(). You'll find detailed information in Qt documentation about custom widgets and layouts.

How to connect a QRadioButton of a QTableWidget cell with another QTableWidget cell using an arrow?

I have a QListWidget and a QGraphicsView both subclassed to overwrite some of their members. I prepared a minimal verifiable example showing the problem I have here
From the QListWidget I can drag and drop specific field (represented by a QTableWidget) and drop them into a QGraphicsView and in order to do that I am using a QGraphicsProxyWidget approach as shown below.
The Problem
Now, how do I connect 2 QRadioButton inside cell of a QTableWidget with another cell of another QTableWidget?
It is important to mention that the green QGraphicsRectItem it is used to move around the QTableWidget as well as adjusting its dimension.
Below the result I was able to arrive so far:
And below the expected result I have been trying to achieve:
Below the most important part of the code:
scene.h
#ifndef SCENE_H
#define SCENE_H
#include <QGraphicsScene>
class Scene : public QGraphicsScene
{
public:
Scene(QObject *parent = nullptr);
protected:
void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
void dropEvent(QGraphicsSceneDragDropEvent *event);
};
#endif // SCENE_H
scene.cpp
#include "arrow.h"
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QTableWidget>
#include <QGraphicsProxyWidget>
#include <QVBoxLayout>
#include <QMetaEnum>
#include <QEvent>
#include <QSizeGrip>
#include <QRadioButton>
Scene::Scene(QObject *parent)
{
setBackgroundBrush(Qt::lightGray);
}
void Scene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) {
if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
event->setAccepted(true);
}
void Scene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) {
if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
event->setAccepted(true);
}
void Scene::dropEvent(QGraphicsSceneDragDropEvent *event) {
QByteArray encoded =
event->mimeData()->data("application/x-qabstractitemmodeldatalist");
QDataStream stream(&encoded, QIODevice::ReadOnly);
QStringList rosTables;
QString newString;
while (!stream.atEnd()) {
int row, col;
QMap<int, QVariant> roleDataMap;
stream >> row >> col >> roleDataMap;
rosTables << roleDataMap[Qt::DisplayRole].toString();
}
for (const QString &tableType : rosTables) {
if (tableType == "Images") {
QPoint initPos(0, 0);
auto *wgt = new CustomTableWidget;
auto *proxyControl = addRect(0, 0, 0, 0, QPen(Qt::black),
QBrush(Qt::darkGreen));
auto *sizeGrip = new QSizeGrip(wgt);
auto *layout = new QHBoxLayout(wgt);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom);
connect(wgt, &CustomTableWidget::sizeChanged, [wgt, proxyControl](){
proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
});
wgt->setColumnCount(4);
wgt->setRowCount(4);
for (int ridx = 0; ridx < wgt->rowCount(); ridx++) {
for (int cidx = 0; cidx < wgt->columnCount(); cidx++) {
QRadioButton *radio1, *radio2;
auto* item = new QTableWidgetItem();
item->setText(QString("%1").arg(ridx));
wgt->setItem(ridx,cidx,item);
radio1 = new QRadioButton;
radio2 = new QRadioButton;
wgt->setCellWidget(cidx, 0, radio1);
wgt->setCellWidget(cidx, 3, radio2);
Arrow *arrow = new Arrow;
}
}
auto *const proxy = addWidget(wgt);
proxy->setPos(initPos.x(), initPos.y()
+ proxyControl->rect().height());
proxy->setParentItem(proxyControl);
proxyControl->setPos(initPos.x(), initPos.y());
proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);
proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
}
}
}
diagramitem.h
#ifndef DIAGRAMITEM_H
#define DIAGRAMITEM_H
#include <QGraphicsPolygonItem>
class Arrow;
class DiagramItem : public QGraphicsPolygonItem
{
public:
DiagramItem(QMenu *contextMenu, QGraphicsItem *parent = Q_NULLPTR);
void removeArrow(Arrow *arrow);
void removeArrows();
void addArrow(Arrow *arrow);
QPixmap image() const;
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
private:
QPolygonF myPolygon;
QList<Arrow*> arrows;
QMenu *myContextMenu;
};
#endif // DIAGRAMITEM_H
diagramitem.cpp
#include "diagramitem.h"
#include "arrow.h"
#include <QPainter>
#include <QGraphicsScene>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
DiagramItem::DiagramItem(QMenu *contextMenu, QGraphicsItem *parent) : QGraphicsPolygonItem(parent)
{
myContextMenu = contextMenu;
setPolygon(myPolygon);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
}
void DiagramItem::removeArrow(Arrow *arrow)
{
int index = arrows.indexOf(arrow);
if (index != -1)
arrows.removeAt(index);
}
void DiagramItem::removeArrows()
{
foreach (Arrow *arrow, arrows) {
arrow->startItem()->removeArrow(arrow);
arrow->endItem()->removeArrow(arrow);
scene()->removeItem(arrow);
delete arrow;
}
}
void DiagramItem::addArrow(Arrow *arrow)
{
arrows.append(arrow);
}
void DiagramItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
scene()->clearSelection();
setSelected(true);
myContextMenu->exec(event->screenPos());
}
QVariant DiagramItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemPositionChange) {
foreach (Arrow *arrow, arrows) {
arrow->updatePosition();
}
}
return value;
}
arrow.h
#ifndef ARROW_H
#define ARROW_H
#include <QGraphicsLineItem>
#include "diagramitem.h"
class Arrow : public QGraphicsLineItem
{
public:
enum { Type = UserType + 4 };
Arrow(DiagramItem *startItem, DiagramItem *endItem,
QGraphicsItem *parent = nullptr);
DiagramItem *startItem() const { return myStartItem; }
DiagramItem *endItem() const { return myEndItem; }
QPainterPath shape() const override;
void setColor(const QColor &color) {
myColor = color;
}
int type() const override { return Type; }
void updatePosition();
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
private:
QColor myColor;
DiagramItem *myStartItem;
DiagramItem *myEndItem;
QPolygonF arrowHead;
};
#endif // ARROW_H
arrow.cpp
#include "arrow.h"
#include <QPen>
#include <QPainter>
#include "qmath.h"
Arrow::Arrow(DiagramItem *startItem, DiagramItem *endItem, QGraphicsItem *parent) : QGraphicsLineItem(parent)
{
myStartItem = startItem;
myEndItem = endItem;
myColor = Qt::GlobalColor::black;
setPen(QPen(myColor, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
setFlag(QGraphicsItem::ItemIsSelectable, true);
}
QPainterPath Arrow::shape() const
{
QPainterPath path = QGraphicsLineItem::shape();
path.addPolygon(arrowHead);
return path;
}
void Arrow::updatePosition()
{
QLineF line(mapFromItem(myStartItem, 0, 0), mapFromItem(myEndItem, 0, 0));
setLine(line);
}
void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
if (myStartItem->collidesWithItem(myEndItem))
return;
QPen myPen = pen();
myPen.setColor(myColor);
qreal arrowSize = 20;
painter->setPen(myPen);
painter->setBrush(myColor);
QLineF centerLine(myStartItem->pos(), myEndItem->pos());
QPolygonF endPolygon = myEndItem->polygon();
QPointF p1 = endPolygon.first() + myEndItem->pos();
QPointF p2;
QPointF intersectPoint;
QLineF polyLine;
for (int i = 1; i < endPolygon.count(); ++i) {
p2 = endPolygon.at(i) + myEndItem->pos();
polyLine = QLineF(p1, p2);
QLineF::IntersectType intersectType =
polyLine.intersect(centerLine, &intersectPoint);
if (intersectType == QLineF::BoundedIntersection)
break;
p1 = p2;
}
setLine(QLineF(intersectPoint, myStartItem->pos()));
double angle = std::atan2(-line().dy(), line().dx());
QPointF arrowP1 = line().p1() + QPointF(sin(angle + M_PI / 3) * arrowSize,
cos(angle + M_PI / 3) * arrowSize);
QPointF arrowP2 = line().p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize,
cos(angle + M_PI - M_PI / 3) * arrowSize);
arrowHead.clear();
arrowHead << line().p1() << arrowP1 << arrowP2;
painter->drawLine(line());
painter->drawPolygon(arrowHead);
if (isSelected()) {
painter->setPen(QPen(myColor, 1, Qt::DashLine));
QLineF myLine = line();
myLine.translate(0, 4.0);
painter->drawLine(myLine);
myLine.translate(0,-8.0);
painter->drawLine(myLine);
}
}
What I have done so far to solve the problem:
1) I came across this post which was useful to understand the initial idea on how to do that, but it didn't really provide a way, or an implementation idea on how to best proceed
2) I researched the official documentation and before asking this question I went through the whole Diagram Scene example provided and understood how to create an Arrow object. The documentation about that was very good and made me understand how the graphics line item has to be formed.
However I was not able (coming back to my example) how to make "aware" the QRadioButton that I am trying to use its center as starting point for an arrow ad, therefore, how do I make "aware" the destination QRadioButton in another cell that it has to be connected there?
Below a particular of what I mean:
So basically the start point of the QRadioButton change color (or style) and the arrival point also change color.
3) I thought that the Arrow object has to be created inside the subclassed QGraphicsScene since it already handles the mouse events.
4) Despite what I tried so far I could not find any other useful help. Although I am still investigating how to do that.
If anyone has ever been in the same situation please provide guidance on how to better proceed to solve this problem and find a solution to this issue.
Solution
When a start and end radio buttons are checked, you need to create the arrow with those buttons as start and end nodes, e.g.:
void Backend::onInputRadioButton(bool checked)
{
m_endNode = checked ? static_cast<QRadioButton *>(sender()) : nullptr;
if (m_startNode && m_endNode)
m_scene->addItem(new ArrowItem(m_startNode, m_endNode));
}
Then you need to connect the signal of the top-most graphics items, which hold the tables, with the updatePosition slot of the ArrowItem, e.g.:
connect(m_startItem->property("item").value<MovableItem *>(),
&MovableItem::itemMoved, this, &ArrowItem::updatePosition);
connect(m_endItem->property("item").value<MovableItem *>(),
&MovableItem::itemMoved, this, &ArrowItem::updatePosition);
Note: I am using a property to hold a reference to the container item.
Finally, you need to update the arrow line, e.g.:
void ArrowItem::updatePosition()
{
QPointF offset(7, 15);
QPointF p1 = m_startItem->property("item").value<MovableItem *>()->pos()
+ m_startItem->parentWidget()->mapToParent(m_startItem->pos())
+ offset;
QPointF p2 = m_endItem->property("item").value<MovableItem *>()->pos()
+ m_endItem->parentWidget()->mapToParent(m_endItem->pos())
+ offset;
setLine(QLineF(p1, p2));
}
Example
I have dared to suggest improvements in your code. You can find the complete example I wrote for you on GitHub.
Result
The provided example produces the following result:
Note: The arrow heads are missing. Check once again the Diagram Scene Example to get an idea of how to draw them.

How to draw a linear gradient arc with Qt QPainter?

I'm trying to develop a custom QProgressBar that will look like the following image :
I created a class that extends QProgressBar and implemented the paintEvent() :
void CircularProgressBar::paintEvent(QPaintEvent*) {
int progress = this->value();
int progressInDegrees = (double)(progress*360)/100;
int barWidth = 20;
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, barWidth, Qt::SolidLine,Qt::RoundCap));
painter.drawArc(barWidth/2, barWidth/2, this->width() - barWidth, this->height() - barWidth,
90*16, progressInDegrees*-16);}
This works great to draw the circular progress bar, but I'm having trouble with the linear gradient color of the bar. I tried creating a QPen with a QLinearGradient object and I tried setting the QPainter brush to a QLinearGradient object, but neither strategy worked. Is it possible to draw an arc with QPainter that has a linear gradient color?
I know this is an old question but I came across it some days ago and I think I have a solution. What you want is to create a conical gradient and clip the disk you want to use as circular loading bar. Here is an example:
widget.h:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class QPaintEvent;
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void setLoadingAngle(int loadingAngle);
int loadingAngle() const;
void setDiscWidth(int width);
int discWidth() const;
protected:
void paintEvent(QPaintEvent *);
private:
int m_loadingAngle;
int m_width;
};
#endif // WIDGET_H
widget.cpp:
#include "widget.h"
#include <QPaintEvent>
#include <QPainter>
#include <QConicalGradient>
#include <QPen>
Widget::Widget(QWidget *parent) :
QWidget(parent),
m_loadingAngle(0),
m_width(0)
{
}
Widget::~Widget()
{
}
void Widget::setLoadingAngle(int loadingAngle)
{
m_loadingAngle = loadingAngle;
}
int Widget::loadingAngle() const
{
return m_loadingAngle;
}
void Widget::setDiscWidth(int width)
{
m_width = width;
}
int Widget::discWidth() const
{
return m_width;
}
void Widget::paintEvent(QPaintEvent *)
{
QRect drawingRect;
drawingRect.setX(rect().x() + m_width);
drawingRect.setY(rect().y() + m_width);
drawingRect.setWidth(rect().width() - m_width * 2);
drawingRect.setHeight(rect().height() - m_width * 2);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QConicalGradient gradient;
gradient.setCenter(drawingRect.center());
gradient.setAngle(90);
gradient.setColorAt(0, QColor(178, 255, 246));
gradient.setColorAt(1, QColor(5, 44, 50));
int arcLengthApproximation = m_width + m_width / 3;
QPen pen(QBrush(gradient), m_width);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.drawArc(drawingRect, 90 * 16 - arcLengthApproximation, -m_loadingAngle * 16);
}
main.cpp:
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.setDiscWidth(20);
w.setLoadingAngle(270);
w.show();
return a.exec();
}
And the result is:
Of course, it is not the complete and exact solution but I think it is everything you need to know in order to achieve what you want. The rest are details not hard to implement.
This solution is not exactly what you're after; the gradient goes from top to bottom, rather than around the circle:
#include <QtWidgets>
class Widget : public QWidget
{
public:
Widget() {
resize(200, 200);
}
void paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
const QRectF bounds(0, 0, width(), height());
painter.fillRect(bounds, "#1c1c1c");
QPen pen;
pen.setCapStyle(Qt::RoundCap);
pen.setWidth(20);
QLinearGradient gradient;
gradient.setStart(bounds.width() / 2, 0);
gradient.setFinalStop(bounds.width() / 2, bounds.height());
gradient.setColorAt(0, "#1c1c1c");
gradient.setColorAt(1, "#28ecd6");
QBrush brush(gradient);
pen.setBrush(brush);
painter.setPen(pen);
QRectF rect = QRectF(pen.widthF() / 2.0, pen.widthF() / 2.0, width() - pen.widthF(), height() - pen.widthF());
painter.drawArc(rect, 90 * 16, 0.65 * -360 * 16);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}
However, it is an arc with a linear gradient! :p

Need to draw Transparent qimage that includes drawing of a circle using Qt

I am trying to draw a transparent image using QImage but everytime it gives black background. I have a image background,on that I want to draw a circle which should be trasparent(with no background).How can I do that?
I have used this code
QImage image(size, QImage::Format_ARGB32);
image.fill(qRgba(0,0,0,0));
// Pick an arbitrary size for the circle
const int centerX = size.width() / 2;
const int centerY = size.height() / 2;
const int radius = std::min(centerX, centerY) * 2 / 3;
const int diameter = radius * 2;
// Draw the circle!
QPainter painter(&image);
painter.setPen(Qt::yellow);
painter.drawEllipse(centerX-radius, centerY-radius, diameter, diameter);
http://qt-project.org/doc/qt-4.8/qpainter.html#settings
http://qt-project.org/doc/qt-4.8/qpainter.html#setBrush
The painter's brush defines how shapes are filled.
Hope that helps.
EDIT: Added an awesome example:
Basically what happens below, is the window is set to have a background color (so that the alpha value of the QImage is noticeable and predicable). The QImage is initialized to have a color with an alpha value less than 255. The image gets painted when the widget updates (when shown in the main).
widget.cpp
#include "widget.h"
#include <QImage>
#include <QPainter>
#include <QPalette>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
init_image();
QPalette p = this->palette();
p.setColor(QPalette::Background, Qt::white);
this->setPalette(p);
}
void Widget::init_image()
{
image = new QImage(200, 200, QImage::Format_ARGB32);
int opacity = 50;// Set this between 0 and 255
image->fill(QColor(0,0,0,opacity));
QPainter painter (image);
painter.setPen(Qt::green);
painter.drawEllipse(10, 10, 100, 100);
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent * e)
{
QPainter painter(this);
painter.drawImage(0,0, *image,0,0,-1,-1,Qt::AutoColor);
}
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPaintEvent>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
void init_image();
public slots:
void paintEvent(QPaintEvent *);
private:
QImage * image;
};
#endif // WIDGET_H
Main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

Horde3D Particle System not rendering in Qt OpenGL

Horde3d come with 2 samples, compiled with GLFW. One of them, Knight, shows a particle emitter. I've ported the samples to Qt, with a thin layer I wrote that leave unchanged the applicative code (i.e. scene setup, rendering and events handling).
Indeed, the functionality is ok, except the particle emitter doesn't show. I can't see anything specific in GLFW initialization, and I tried some setting I found in Qt, without success. Horde3d takes care of OpenGL interface, and expose a higher level, clean C interface. Any clue?
EDIT : Most Rilevant Sources of qtKnight.pro
here (cleaned :) main.cpp
#include "glwidget.h"
#include <QApplication>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
GLWidget glw;
glw.show();
return app.exec();
}
here glWidget.h
#ifndef GL_WIDGET_H
#define GL_WIDGET_H
#include <QtOpenGL>
#include <QTimer>
#include <QKeyEvent>
#include <Horde3D.h>
#include <Horde3DUtils.h>
#include <sstream>
#include <app.h>
class GLWidget : public QGLWidget, Application {
Q_OBJECT
public:
GLWidget();
~GLWidget();
QSize minimumSizeHint() const { return sizeHint(); }
QSize sizeHint() const { return QSize(640, 480); }
protected:
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *e) { keyEvent(e, true); QGLWidget::keyPressEvent(e); }
void keyReleaseEvent(QKeyEvent *e) { keyEvent(e, false); QGLWidget::keyReleaseEvent(e); }
void keyEvent(QKeyEvent *, bool);
public slots:
void appLoop() { updateGL(); }
private:
QPoint lastPos;
QTimer evloop;
};
#endif
and here glWidget.cpp
#include "glwidget.h"
#include <stdexcept>
#include <QtDebug>
#include <QTextStream>
#include <QGLFormat>
GLWidget::GLWidget() :
QGLWidget(QGLFormat(QGL::AlphaChannel | QGL::SampleBuffers)),
Application("/home/carlo/horde3d/SDK_1.0.0_Beta5/Horde3D/Binaries/Content")
{
connect(&evloop, SIGNAL(timeout()), this, SLOT(appLoop()));
evloop.start(0);
}
GLWidget::~GLWidget()
{
h3dutDumpMessages();
h3dRelease();
}
void GLWidget::initializeGL()
{
if (!init())
throw std::runtime_error("Could not initialize renderer");
}
void GLWidget::paintGL()
{
keyStateHandler();
mainLoop(30);
}
void GLWidget::resizeGL(int width, int height)
{
Application::resize(width, height);
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
QPoint cPos = event->pos();
float dX = cPos.x() - lastPos.x(); //event->x() - lastPos.x();
float dY = cPos.y() - lastPos.y(); //event->y() - lastPos.y();
Application::mouseMoveEvent(dX, dY);
lastPos = cPos;
}
void GLWidget::mouseReleaseEvent(QMouseEvent * /* event */)
{
}
void GLWidget::keyEvent(QKeyEvent *k, bool on_off)
{
#define setK(X,Y) case Qt::X: setKeyState(Y, on_off); break;
#define setF(X) case Qt::Key_##X: setKeyState(X, on_off); break;
#define R(X, Y) (v >= #X[0] && v <= #Y[0])
int v = k->key();
switch (v) {
case Qt::Key_F1: if (on_off) showFullScreen(); break;
case Qt::Key_F2: if (on_off) showNormal(); break;
setF(F3)
setF(F6)
setF(F7)
setF(F8)
setK(Key_Space, SP)
default:
if (R(A, Z) || R(0, 9))
setKeyState(v, on_off);
}
}
Since you say it is only the particles that aren't shown, it could have to do with alpha blending. I also saw from your code that you don't specify a format when constructing the QGLWidget, in which case the default format is used which explicitly disables the alpha channel.
Though I don't know if this has any effect (shouldn't there always be an A in RGBA?), but maybe this really requests a pixel format where there is no storage for the A channel, in which case things like alpha blending (used for the transparent particles) won't work.
So just try to request it explicitly by using an appropriate format in the QGLWidget constructor:
GLWidget::GLWidget()
: QGLWidget(QGLFormat(QGL::AlphaChannel))
You actually were on the right track with your outcommented QGLFormat argument, but it hasn't anything to do with the HasOverlay option, which you don't really need, as Horde3d does it's own overlay rendering.

Resources