Draw ellipse delay and draw another one - c++

i need to draw a ellipse and after some delay draw another one.
I having trouble doing this.
This is a simplified version of the code that i'm actually doing, but i belive this will help me solve the problem
here is the code.
MyView::MyView()
{
sc = new QGraphicsScene();
this->setSceneRect(0,0,800,600);
this->setFixedSize(800,600);
this->setStyleSheet("QScrollBar {height:0px;}");
this->setStyleSheet("QScrollBar {width:0px;}");
sc->setSceneRect(0,0,800,600);
this->setScene(sc);
}
void MyView::mousePressEvent(QMouseEvent *event)
{
sc->addEllipse(event->x(),event->y(),10,10,QPen(),QBrush(Qt::red));
int i=0;
while(i < 1000000000) // SIMULATING DELAY
i++; //
sc->addEllipse(event->y(),event->x(),10,10,QPen(),QBrush(Qt::blue));
}
class MyView : public QGraphicsView
{
public:
MyView();
QGraphicsScene *sc;
public slots:
void mousePressEvent(QMouseEvent *event);
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyView wv;
wv.show();
return a.exec();
}
the first ellipse is not showing up until the while over and the second one appears
it's doesn't matter how long is the while. Always draw the two ellipses at the same time.

In the main thread of the GUI you should not have tasks that take too much time since they block the eventloop preventing the GUI's own work from being performed. In your case, that while loop can be replaced by a QTimer:
void MyView::mousePressEvent(QMouseEvent *event)
{
QPointF p = mapToScene(event->pos());
sc->addEllipse(QRectF(p, QSizeF(10, 10)), QPen(), QBrush(Qt::red));
// 1000 is the delay in ms
QTimer::singleShot(1000, this, [this, p](){
sc->addEllipse(QRectF(p, QSizeF(10, 10)), QPen(), QBrush(Qt::blue));
});
}

Related

Change circle colour every 5 seconds

I try to create a green circle which every 5 seconds disappears.
Actually, I have the green circle created with the QPainter method. I tried QTimer and others methods but I can't find the good solution.
I overrided the paintEvent function like this :
void MainWindow::paintEvent(QPaintEvent *)
{
QPainter painter(this);
Qt::BrushStyle style = Qt::SolidPattern;
QBrush brush(Qt::green, style);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(brush);
painter.drawEllipse(525, 5, 50, 50);
}
MainWindow::MainWindow() : QWidget()
{
QTimer *ledtimer = new QTimer(this);
connect(ledtimer, SIGNAL(timeout()), this, SLOT(run_led()));
ledtimer->start(5000);
}
I tried to do something like this, but when i'm using run_led, it tells that painter is already removed (i tried in MainWindow class).
I understand the signal function and the timer, I used it in another files, so some tips would be appreciated. Am I supposed to use timers to make circles wink ?
Define a flag boolean that changes every 5 seconds and in paint use a brush as global variable
void MainWindow::paintEvent(QPaintEvent *)
{
....
QBrush brush(myBrush, style);
...
}
and in slot (run_led)
void MainWindow::run_led()
{
c != true;
if(c)
{
myBrush=Qt::green;
}
else
{
myBrush=Qt::gray;
}
}
Assuming your MainWindowinherits QMainWindow
MainWindow::paintEvent(QPaintEvent *) is a function that tells the systems to render your window.
So I let you guess what goes wrong when you override it like this.
But you can put the drawing in a QWidget made for this : QGraphicsView which displays the content of QGraphicsScene .
You should create a slot to do what you want, like this :
void MainWindow::on_led_timer_timeout(){
/*
Do stuff the the QGraphicsScene or QGraphicsView
*/
}
And then connect the correct signal of your QTimer to it :
connect(ledtimer, &QTimer::timeout, this, &MainWindow::on_led_timer_timeout);
class QSimpleLed : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor)
public:
using QWidget::QWidget;
void setColor(const QColor& c) {
if (m_color != m) {
m_color = m;
update();
}
}
QColor color() const;
void paintEvent(QPaintEvent *) override;
private:
QColor m_color;
}
Implementation above should be obvious.
int main(int argc, char* argv[])
{
QApplication app{argc, argv};
QSimpleLed led;
auto animation = new QPropertyAnimation(&led, "color");
animation->setStartValue(Qt::red);
animation->setEndValue(Qt::green);
animation->setLoopCount(-1);
animation->setDuration(5000);
animation->start();
led.show();
return app.exec();
}

Smooth drawing with QPainter

How can I smoothly grow an angle/length-changing Arc with Qt QPainter? Here is minimum code I just created from Qt's Analog Clock Window Example.
The code randomly changes m_value +- 5 in 50-millisecond. This is to simulate the actual behavior I want to achieve. The arc starts at 12 O'clock position and grow counter-clockwise. m_value is scaled to fit 360 degree (12 O'clock to 12 O'clock).
My goal is to smoothly change the length of arc, in real-time, in response to the (simulated) value given, regardless of input value jitters.
I want to accomplish 2 things:
Smooth redraw of the arc. The current code directly redraw the
value at the time. I does not even use sub angle value. The result
is visually noisy at the end of the arc.
Update the drawing in along with V-Sync. So that I don't waste
computation power for non-displayed redraw. I don't know how to
trigger render event by V-Sync, so I've setup 33-millisecond
timer. This is needed when m_value changes in less than 30 msec.
What I don't want
QtQuick. I'm looking for QPainter way to do it.
Platform I'm using:
Qt 5.x
on Debian Linux (If it matters)
#include <QtGui>
#include "rasterwindow.h"
class SmoothArc : public RasterWindow
{
public:
SmoothArc();
protected:
void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE;
void render(QPainter *p) Q_DECL_OVERRIDE;
private:
int m_timerId;
int m_valueTimerId;
int m_value = 50;
};
SmoothArc::SmoothArc()
{
setTitle("Smooth Arc");
resize(200, 200);
m_timerId = startTimer(33);
m_valueTimerId = startTimer(100);
}
void SmoothArc::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_timerId)
renderLater();
if (event->timerId() == m_valueTimerId) {
m_value = m_value + (qrand() % 11 - 5);
if (m_value > 100) m_value = 100;
if (m_value < 0) m_value = 0;
}
}
void SmoothArc::render(QPainter *p)
{
p->setRenderHint(QPainter::Antialiasing);
int side = qMin(width(), height());
p->scale(side / 200.0, side / 200.0);
QRectF rect(10, 10, 180, 180);
QPen pen = p->pen();
pen.setWidth(10);
p->setPen(pen);
p->drawArc(rect, 90*16, (360*(m_value/100.0))*16);
}
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
SmoothArc arc;
arc.show();
return app.exec();
}
Complete code is at https://github.com/yashi/smooth-arc. Usual build process like the following should work.
git clone https://github.com/yashi/smooth-arc.git
cd smooth-arc
qmake
make
./smooth-arc
I'm not familiar with the Qt Gui-only approach to this problem, so I'll show how to do it with Qt Widgets instead.
Smooth redraw of the arc. The current code directly redraw the value at the time. I does not even use sub angle value. The result is visually noisy at the end of the arc.
You can use Qt's animation framework to interpolate property changes:
#include <QtWidgets>
class SmoothArc : public QWidget
{
Q_OBJECT
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged)
public:
SmoothArc();
qreal value() const;
void setValue(qreal value);
signals:
void valueChanged();
protected:
void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE;
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
private:
int m_valueTimerId;
qreal m_value;
QPropertyAnimation m_animation;
};
SmoothArc::SmoothArc()
{
resize(200, 200);
m_valueTimerId = startTimer(100);
m_value = 50;
m_animation.setTargetObject(this);
m_animation.setPropertyName("value");
}
qreal SmoothArc::value() const
{
return m_value;
}
void SmoothArc::setValue(qreal value)
{
if (qFuzzyCompare(value, m_value))
return;
m_value = value;
update();
emit valueChanged();
}
void SmoothArc::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_valueTimerId) {
qreal newValue = m_value + (qrand() % 11 - 5);
if (newValue > 100) newValue = 100;
if (newValue < 0) newValue = 0;
if (m_animation.state() == QPropertyAnimation::Running)
m_animation.stop();
m_animation.setStartValue(m_value);
m_animation.setEndValue(newValue);
m_animation.start();
}
}
void SmoothArc::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
int side = qMin(width(), height());
p.scale(side / 200.0, side / 200.0);
QRectF rect(10, 10, 180, 180);
QPen pen = p.pen();
pen.setWidth(10);
p.setPen(pen);
p.drawArc(rect, 90*16, (360*(m_value/100.0))*16);
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
SmoothArc arc;
arc.show();
return app.exec();
}
#include "main.moc"
Update the drawing in along with V-Sync. So that I don't waste computation power for non-displayed redraw. I don't know how to trigger render event by V-Sync, so I've setup 33-millisecond timer. This is needed when m_value changes in less than 30 msec.
I think that Qt should handle this for you if you use update():
Updates the widget unless updates are disabled or the widget is hidden.
This function does not cause an immediate repaint; instead it schedules a paint event for processing when Qt returns to the main event loop. This permits Qt to optimize for more speed and less flicker than a call to repaint() does.
Calling update() several times normally results in just one paintEvent() call.

QGraphicsRectItem move with mouse. How to?

I have QGraphicsView, QGraphicsScene and QGraphicsRectItem.
QGraphicsRectItem in the QGraphicsScene and the last one in the QGraphicsView. I want to move QGraphicsRectItem with mouse by clicking on it only! But in my implementation it moves if I click on any position on my QGraphicsScene. Whether it is my QGraphicsRectItem or some other place. And the second issue. The item has been moved to the center of the scene. Clicking on it again it starts to move from the home location.
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset); //p1 movable item
}
}
What do I do wrong?
UPDATE:
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
widget.h
#ifndef STEER_H
#define STEER_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMouseEvent>
#include <QPoint>
#include <QGraphicsRectItem>
class Steer : public QGraphicsView
{
Q_OBJECT
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint offset;
public:
explicit Steer(QGraphicsView *parent = 0);
~Steer(){}
public slots:
void mousePressEvent(QMouseEvent * click);
void mouseMoveEvent(QMouseEvent * event);
};
#endif // STEER_H
widget.cpp
#include "widget.h"
#include <QBrush>
Steer::Steer(QGraphicsView *parent)
: QGraphicsView(parent)
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(760, 160, 10, 80);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset);
}
}
I'd try a different approach that is a little easier to understand:
#include <QtWidgets>
class Steer : public QGraphicsView
{
public:
Steer()
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(0, 0, 10, 80);
p1->setX(760);
p1->setY(160);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
protected:
void mousePressEvent(QMouseEvent * click)
{
if (p1->contains(p1->mapFromScene(click->localPos()))) {
lastMousePos = click->pos();
} else {
lastMousePos = QPoint(-1, -1);
}
}
void mouseMoveEvent(QMouseEvent * event)
{
if(!(event->buttons() & Qt::LeftButton)) {
return;
}
if (lastMousePos == QPoint(-1, -1)) {
return;
}
p1->setPos(p1->pos() + (event->localPos() - lastMousePos));
lastMousePos = event->pos();
}
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint lastMousePos;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
There are a few things to point out here:
Don't use setRect() to set the position of a QGraphicsRectItem. It doesn't work the way you think it might. Always use setPos() to change the position of an item.
Rename offset to something more descriptive. I chose lastMousePos. Instead of just updating it once when the mouse is pressed, also update it whenever the mouse is moved. Then, it's simply a matter of getting the difference between the two points and adding that to the position of the item.
Check if the mouse is actually over the item before reacting to move events. If the mouse isn't over the item, you need some way of knowing that, hence the QPoint(-1, -1). You may want to use a separate boolean flag for this purpose. This solves the problem that you saw, where it was possible to click anywhere in the scene to get the item to move.
Also, note the mapFromScene() call: the contains() function works in local coordinates, so we must map the mouse position which is in scene coordinates before testing if it's over the item.
The event functions are not slots, they're virtual, protected functions.
You could also consider handling these events in the items themselves. You don't need to do it from within QGraphicsView, especially if you have more than one of these items that need to be dragged with the mouse.

QOpenGLWidget update() doesn't render scene when multisampling enabled

I'm trying to use anti-aliasing to get shaper edges in my scene. However, when I enable multisampling and I call QOpenGLWidget::update() the scene doesn't update. Although if I click on the scene then an anti-aliased scene is rendered. If I comment out the one line setting the number of samples used then an aliased scene is rendered as normal when calling update.
I set the default surface format in main.cpp I'm using CoreProfile 4.3 with samples set to 4.
#include <QApplication>
#include "widgets/mainwindow.h"
int main(int argc, char *argv[])
{
auto format = QSurfaceFormat();
format.setVersion(4,3);
//If I comment out this line everything works great but with jagged edges
format.setSamples(4);
format.setProfile(QSurfaceFormat::CoreProfile);
QSurfaceFormat::setDefaultFormat(format);
QApplication a(argc, argv);
MainWindow w;
w.show();
w.resize(1200, 800);
return a.exec();
}
In my viewer.cpp which inherits from QOpenGLWidget I enable GL_MULTISAMPLE
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
f->glEnable(GL_MULTISAMPLE);
An example (there are many more) that doesn't render when I call update when using multisampling is when I load in a new mesh.
auto ViewerWidget::importMesh(QString filename) -> void
{
mesh = new Mesh(filename);
initializeGL();
update();
}
However, when I click on the screen then the scene does update. My mouse movement events are below and don't do anything special compared to my mesh import method.
void ViewerWidget::mousePressEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
m_rotationTrackball.push(pixelPosToViewPos(event->pos()), m_scaleTrackball.rotation().conjugate());
}
update();
}
void ViewerWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
m_rotationTrackball.release(pixelPosToViewPos(event->pos()), m_scaleTrackball.rotation().conjugate());
}
update();
}
void ViewerWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton)
{
m_rotationTrackball.move(pixelPosToViewPos(event->pos()), m_scaleTrackball.rotation().conjugate());
}
else
{
m_rotationTrackball.release(pixelPosToViewPos(event->pos()), m_scaleTrackball.rotation().conjugate());
}
update();
}
Any suggestions on how to solve this would be appreciated! Thanks in advance.

Qt crashes/doesn't appear when i use Qthread::sleep to update progress bar

I am kinda new to QT so i am wondering why is this invalid:
I have a progress bar and i want to update it by using a class that inherits QThread.
void mt::run(QProgressBar * asd){
for(int i = 0;i<100;i++){
asd->setValue(i);
QThread::sleep(100);
}
}
mt is a class that inherits QThread. run is overloaded with a QProgressBar argument. My main UI thread will send it's progressbar like this m.run(ui->progressBar);. If i will remove the QThread::sleep(100); then it will work fine but i won't be able to see the increment because the thread will be done so fast. But if i will put a little delay, my screen won't appear at all.
You can access and update GUI elements from the main thread only.
If you want to prepare some data inside a custom thread, you should use the signals/slots mechanism to send that data to your widgets.
Here's a basic example:
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread(QObject *parent = 0);
signals:
void valueChanged(int value);
protected:
void run();
}
void MyThread::run()
{
for (int i = 0; i < 100; i++)
{
emit valueChanged(i);
QThread::sleep(100);
}
}
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent)
{
QHbovLayout *layout = new QHbovLayout(this);
QProgressBar *pb = new QProgressBar;
layout->addWidget(pb);
MyThread *t = new MyThread(this);
connect(t, SIGNAL(valueChanged(int)), pb, SLOT(setValue(int)));
t->start();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// we are in the main thread here, so we can create a widget
MyWidget w;
w.show();
return a.exec();
}
QThread::sleep(100);
You are telling it to sleep for a 100 seconds - that's quite a long time. Perhaps you meant QThread::msleep(100)?