QStateMachine event loop with animation - c++

I want to create a endless loop with QStateMachine in which I also need animations.
QColor leastTransparent, mostTransparent = color();
leastTransparent.setAlpha(250);
mostTransparent.setAlpha(150);
QState *s1 = new QState();
s1->assignProperty(this, "color", leastTransparent);
QState *s2 = new QState();
s2->assignProperty(this, "color", mostTransparent);
QSignalTransition *transition = s1->addTransition( this, SIGNAL(triggerSignal()),s2);
QSignalTransition *transition2 = s2->addTransition(s2, SIGNAL(entered),s1);
QPropertyAnimation* animation = new QPropertyAnimation( this, "color");
animation->setDuration( 5000 );
transition->addAnimation(animation);
QPropertyAnimation* animation2 = new QPropertyAnimation( this, "color");
animation2->setDuration(10000);
transition2->addAnimation(animation2);
m_stateMachineAnimation->addState(s1);
m_stateMachineAnimation->addState(s2);
m_stateMachineAnimation->setInitialState(s1);
m_stateMachineAnimation->setGlobalRestorePolicy(QStateMachine::RestoreProperties);
m_stateMachineAnimation->start();
What I expect here is the color will get more opaque for first 5 seconds after "triggerSignal". And state will be "s2". And than "s2" 's enter signal be triggered and it will get more and more transparent for 10 seconds.
But instead I am having s2 trigger immediately without waiting 5 seconds right after the "triggerSignal" and than immediately s1 is being triggered again without waiting 10 seconds.
Why my duration is not taken into account by QStateMachine. How can I achieve such a animation with QStateMachine

You seem to expect the animation to create some sort of an in-between state. It does no such thing. The transition merely triggers the animation. You transition immediately from s2 to s1, giving no time for animations to finish. Instead, you need to explicitly trigger the subsequent transition when the final values of the properties are set. The QState::propertiesAssigned signal is most useful for this purpose. Alternatively, you could use the animation's finished() signal.
In the example below, click within the window to start the animation loop:
// https://github.com/KubaO/stackoverflown/tree/master/questions/statemachine-animation-42682462
#include <QtWidgets>
const char kColor[] = "color";
class Widget : public QWidget {
Q_OBJECT
Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
QColor m_color{Qt::blue};
QStateMachine m_machine{this};
QState s0{&m_machine}, s1{&m_machine}, s2{&m_machine};
QEventTransition t01{this, QEvent::MouseButtonPress};
QPropertyAnimation anim_s1{this, kColor}, anim_s2{this, kColor};
void paintEvent(QPaintEvent *) override {
QPainter{this}.fillRect(rect(), m_color);
}
Q_SIGNAL void colorChanged(const QColor &);
public:
Widget() {
connect(this, &Widget::colorChanged, [this]{ update(); });
s1.assignProperty(this, kColor, QColor{Qt::red});
s2.assignProperty(this, kColor, QColor{Qt::green});
t01.setTargetState(&s1);
s0.addTransition(&t01); t01.addAnimation(&anim_s1);
s1.addTransition(&s1, &QState::propertiesAssigned, &s2)->addAnimation(&anim_s2);
s2.addTransition(&s2, &QState::propertiesAssigned, &s1)->addAnimation(&anim_s1);
anim_s1.setDuration(1000);
anim_s2.setDuration(2000);
m_machine.setInitialState(&s0);
m_machine.start();
}
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
Widget w;
w.setFixedSize(300, 200);
w.show();
return app.exec();
}
#include "main.moc"
As an aside, this demonstrates that the animation interpolates RGB values, causing the color to go dark between red and blue as the values go from (1,0,0) through (.5,.5,0) onto (0,1,0). For human consumption, it'd make more sense to interpolate HSV, so that the value (brightness) stays the same, and only the hue (what we humans really call "color") changes.

Related

Qt GraphicsScene XOR Line or Line in separate layer?

I have started to learn Qt, and try to improve my basic C++ skills.
In GraphicsScene, I try to draw a line by using the mouse (mouse events).
When I start drawing a line in GraphicsScene, a thin dashed line is drawn from the origin, where I clicked first to the current mouse position and moves with the mouse, before the second point is clicked. To erase it, I draw it in black. If I hover over already draw lines, you will see the black drawn lines on them. To undraw it without leaving marks, an XOR operation on GraphicsScene would come in handy, or if I could draw in a different layer and not touching the other layer could be handy. But I couldn't yet figure how to do it. The example is on https://github.com/JackBerkhout/QT_Draw001
In line.cpp is the function setLineP2(int x, int y), which draws and erases that thin dashed line.
Can anybody help me with this, please?
The major misconception is thinking of a QGraphicsScene as some sort of a bitmap: it's not! It is a collection of items that can render themselves, and a spatial index for them. In a scene, if you wish to delete/hide something, you must not overpaint it - instead simply delete/hide the item in question as desired. The scene will handle all the details - that's what it's for
You must forget about GDI-anything at this point. You're not painting on the raw DC here. Even when using raw GDI, you do not want to paint on the window's DC as that flickers, you should paint on a bitmap and blit the bitmap to the window.
For example, your eraseScene method adds a rectangle on top of the scene, wasting memory and resources as all the previous items are retained (you can iterate through them), whereas all it should do is to clear the scene (or its equivalent):
void MainWindow::eraseScreen(void)
{
[...]
scene->addRect(0, 0, width()+1000, height()+1000, pen, brush);
}
vs. the correct:
void MainWindow::eraseScreen(void)
{
scene->clear();
}
Below is a complete example that approximates what you presumably meant to do in your code. It is 120 lines long. It was somewhat hard to figure out what exactly you meant to do as your code is so convoluted - it's useful to describe the exact behavior in simple terms in the question.
The example uses QPainterPath to keep a list of MoveTo and LineTo elements that a QPainterPathItem renders. It also uses a QGraphicsLineItem to display the transient line.
The MyScene::PathUpdater is used to enclose the context where a path is modified, and ensure that proper pre- and post-conditions are maintained. Namely:
Since QPainterPath is implicitly shared, you should clear the path held by QGraphicsPathItem to avoid an unnecessary implicit copy. That's the precondition necessary before modifying m_path.
After m_path has been modified, the path item must be updated, as well as a new status emitted.
The following other points are worth noting:
Holding the members by value leads to a notable absence of any memory management code (!) - the compiler does it all for us. You won't find a single new or delete anywhere. They are not necessary, and we're paying no additional cost for not doing this manually. Modern C++ should look exactly like this.
The clear split between the display MainWindow and MyScene. The MainWindow knows nothing about the specifics of MyScene, and vice-versa. The code within main acts as an adapter between the two.
The leveraging of C++11.
Succinct style necessary for SO test cases and examples: for learning it's best to keep it all in one file to easily see all the parts of the code. It's only 120 lines, vs. more than twice that if split across files. Our brains leverage the locality of reference. By splitting the code you're making it harder for yourself to comprehend.
See also
Another demo of interactive item creation.
A more advanced example of status notifications.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scene-polygon-7727656
#include <QtWidgets>
class MainWindow : public QWidget
{
Q_OBJECT
QGridLayout m_layout{this};
QPushButton m_new{"New"};
QPushButton m_erase{"Erase All"};
QLabel m_label;
QGraphicsView m_view;
public:
MainWindow() {
m_layout.addWidget(&m_new, 0, 0);
m_layout.addWidget(&m_erase, 0, 1);
m_layout.addWidget(&m_label, 0, 2);
m_layout.addWidget(&m_view, 1, 0, 1, 3);
m_view.setBackgroundBrush(Qt::black);
m_view.setAlignment(Qt::AlignBottom | Qt::AlignLeft);
m_view.scale(1, -1);
connect(&m_new, &QPushButton::clicked, this, &MainWindow::newItem);
connect(&m_erase, &QPushButton::clicked, this, &MainWindow::clearScene);
}
void setScene(QGraphicsScene * scene) {
m_view.setScene(scene);
}
Q_SIGNAL void newItem();
Q_SIGNAL void clearScene();
Q_SLOT void setText(const QString & text) { m_label.setText(text); }
};
class MyScene : public QGraphicsScene {
Q_OBJECT
public:
struct Status {
int paths;
int elements;
};
private:
bool m_newItem = {};
Status m_status = {0, 0};
QPainterPath m_path;
QGraphicsPathItem m_pathItem;
QGraphicsLineItem m_lineItem;
struct PathUpdater {
Q_DISABLE_COPY(PathUpdater)
MyScene & s;
PathUpdater(MyScene & scene) : s(scene) {
s.m_pathItem.setPath({}); // avoid a copy-on-write
}
~PathUpdater() {
s.m_pathItem.setPath(s.m_path);
s.m_status = {0, s.m_path.elementCount()};
for (auto i = 0; i < s.m_status.elements; ++i) {
auto element = s.m_path.elementAt(i);
if (element.type == QPainterPath::MoveToElement)
s.m_status.paths++;
}
emit s.statusChanged(s.m_status);
}
};
void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
PathUpdater updater(*this);
auto pos = event->scenePos();
m_lineItem.setLine(0, 0, pos.x(), pos.y());
m_lineItem.setVisible(true);
if (m_path.elementCount() == 0 || m_newItem)
m_path.moveTo(pos);
m_path.lineTo(pos.x()+1,pos.y()+1); // otherwise lineTo is a NOP
m_newItem = {};
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
PathUpdater updater(*this);
auto pos = event->scenePos();
m_lineItem.setLine(0, 0, pos.x(), pos.y());
m_path.setElementPositionAt(m_path.elementCount()-1, pos.x(), pos.y());
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override {
m_lineItem.setVisible(false);
}
public:
MyScene() {
addItem(&m_pathItem);
addItem(&m_lineItem);
m_pathItem.setPen({Qt::red});
m_pathItem.setBrush(Qt::NoBrush);
m_lineItem.setPen({Qt::white});
m_lineItem.setVisible(false);
}
Q_SLOT void clear() {
PathUpdater updater(*this);
m_path = {};
}
Q_SLOT void newItem() {
m_newItem = true;
}
Q_SIGNAL void statusChanged(const MyScene::Status &);
Status status() const { return m_status; }
};
int main(int argc, char *argv[])
{
using Q = QObject;
QApplication app{argc, argv};
MainWindow w;
MyScene scene;
w.setMinimumSize(600, 600);
w.setScene(&scene);
Q::connect(&w, &MainWindow::clearScene, &scene, &MyScene::clear);
Q::connect(&w, &MainWindow::newItem, &scene, &MyScene::newItem);
auto onStatus = [&](const MyScene::Status & s){
w.setText(QStringLiteral("Paths: %1 Elements: %2").arg(s.paths).arg(s.elements));
};
Q::connect(&scene, &MyScene::statusChanged, onStatus);
onStatus(scene.status());
w.show();
return app.exec();
}
#include "main.moc"

How to update/redraw QChart after data is added to QLineSeries?

I am generating some data that I want to chart using QChart & friends. This is my first time using QChart, and so basically what I did was copy the QLineSeries Example and modify it to my needs. My current code looks like this:
quint64 last=0;
quint64 *lastp=&last;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, series( nullptr )
{
ui->setupUi(this);
QChart *chart = new QChart();
series=new QLineSeries(chart);
chart->legend()->hide();
chart->addSeries(series);
chart->createDefaultAxes();
chart->setTitle("Simple line chart example");
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
setCentralWidget(chartView);
GeneticTask *gTask = new GeneticTask();
connect(gTask, &GeneticTask::point, this, [=](QPointF pt) {
// New point added to series
*series<<pt;
// Limit updates to once per second
quint64 now=QDateTime::currentMSecsSinceEpoch();
if(now-(*lastp)>1000) {
qDebug()<<"UPDATE";
// [A] WHAT TO PUT HERE TO HAVE CHART REDRAW WITH NEW DATA?
*lastp=now;
}
}
);
QThreadPool::globalInstance()->start(gTask);
}
When I run this code I would expect my new data to show up in the graph, but it does not, so my question is: How can I have the chart update to show the new data? In other words, what should I put in the code where the comment reads [A]?
Appending a value to QLineSeries using the operator << or the append method should repaint the graph. If it does not happen form some reason, you could trying calling the repaint method on the QChartView.
Here is some code that will center the data once it is added with a cap of at most once per second:
// Global or class scope or
qreal max=-10000000000;
qreal min=-max;
qreal *maxp=&max;
qreal *minp=&min;
// Same scope as before
connect(gTask, &GeneticTask::point, this, [=](QPointF pt) {
if(pt.y()>*maxp) {
*maxp=pt.y();
}
if(pt.y()<*minp) {
*minp=pt.y();
}
*series<<pt;
quint64 now=QDateTime::currentMSecsSinceEpoch();
if(now-(*lastp)>1000) {
qDebug()<<"UPDATE";
chart->axisX()->setRange(0,series->count());
chart->axisY()->setRange(*minp,*maxp);
*lastp=now;
}
}
);
Little correction to the answer above. Qt Documentation says:
void QWidget::repaint()
Repaints the widget directly by calling
paintEvent() immediately, unless updates are disabled or the widget is
hidden. We suggest only using repaint() if you need an immediate
repaint, for example during animation. In almost all circumstances
update() is better, as it permits Qt to optimize for speed and
minimize flicker.
Warning: If you call repaint() in a function which
may itself be called from paintEvent(), you may get infinite
recursion. The update() function never causes recursion.
As result, QChartView::update() works for me.

How to check which image is set in my QLabel?

I have a Qt application where I need to show a blinking LED and for that I need to use some png image of off and on led.I created a Qlabel and used setstylesheet to display the image. I created a timer and connected the signal to a slot. Now the problem is how do I know if the current displayed image is OFF led or ON led.
I have many led in GUI so is there any better way to check this?
Don't bother trying to compare the image, just store a variable of the state of the LED. When the timer triggers you change the state of the variable and set the QImage accordingly.
// assuming a boolean variable
var = !var;
if(var)
label->setImage(":/images/imageOn");
else
label->setImage(":/images/imageOff");
This assumes the images imageOn and imageOff have been added to a Qt resource file and are under an 'images' prefix.
It is good practise to separate logic from its visual representation.
You can leverage the property mechanism to store the index of the next image to be used. A QLabel is-a QObject. Objects can have arbitrary properties assigned to them.
You also don't need to use style sheets to set image on a label. It's a premature pessimization because the stylesheet needs to be parsed every time you set it. If you're not using stylesheets for other purposes, to set an image on a label simply use setPixmap.
For example (Qt 5, C++11):
#include <QApplication>
#include <QTimer>
#include <QLabel>
#include <QImage>
#include <QPainter>
void blink(QLabel * label, const QList<QImage> & images)
{
const char * const prop = "imageIndex";
Q_ASSERT(!images.isEmpty());
if (label->property(prop).isNull()) {
// We're setting the image for the first time
label->setProperty(prop, images.size());
}
int i = (label->property(prop).toInt() + 1) % images.size();
label->setPixmap(QPixmap::fromImage(images[i]));
label->setProperty(prop, i);
}
QImage textImage(const QString & text, int size = 64)
{
QImage image(size, size, QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter p(&image);
p.setFont(QFont("helvetica", 20));
QTextOption opt;
opt.setAlignment(Qt::AlignCenter);
p.drawText(image.rect(), text, opt);
return image;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QList<QImage> images;
QLabel label;
QTimer timer;
images << textImage("0") << textImage("1") << textImage("2") << textImage("3");
blink(&label, images);
timer.start(250);
QObject::connect(&timer, &QTimer::timeout, [&]{ blink(&label, images); });
label.show();
return a.exec();
}

Sleep inside a loop that uses paintevent in qt c++

Basically what I wanna do is to draw rectangles for each number in my list. The bigger the number is, the larger the rectangle is.
My problem is when I actually wanna do it, step-by-step, and waiting a few seconds between every drawing. I've looked out for a few solutions but I can't get them to work for this particular case. I saw I could use fflush to release whatever it's in the buffer but I don't know how I can use it for this.
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern));
int weight=300/lista.size;
int posx=weight;
for (int i=1; i<=lista.size; i++){
List_node * node = list.get_element_at(i);
int num=node->getValue(); //this returns the value of the node
if (i==3){
painter.setBrush(QBrush(Qt::red, Qt::SolidPattern)); // this line is to draw a rectangle with a different color. Testing purposes.
}
painter.drawRect(posx,400-(num*10),weight,num*10);
sleep(1); //this sleep isn't working correctly.
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern));
posx+=weight;
}
Any help would be really appreciated.
sleep() won't work for this -- it blocks the Qt event loop and keeps Qt from doing its job while it is sleeping.
What you need to do is keep one or more member variables to remember the current state of the image you want to draw, and implement paintEvent() to draw that current single image only. paintEvent() (like every function running in Qt's GUI thread) should always return immediately, and never sleep or block.
Then, to implement the animation part of things, set up a QTimer object to call a slot for you at regular intervals (e.g. once every 1000mS, or however often you like). Implement that slot to adjust your member variables to their next state in the animation-sequence (e.g. rectangle_size++ or whatever) and then call update() on your widget. update() will tell Qt to call paintEvent() again on your widget as soon as possible, so your display will be updated to the next frame very shortly after your slot method returns.
Below is a trivial example of the technique; when run it shows a red rectangle getting larger and smaller:
// begin demo.h
#include <QWidget>
#include <QTimer>
class DemoObj : public QWidget
{
Q_OBJECT
public:
DemoObj();
virtual void paintEvent(QPaintEvent * e);
public slots:
void AdvanceState();
private:
QTimer _timer;
int _rectSize;
int _growthDirection;
};
// begin demo.cpp
#include <QApplication>
#include <QPainter>
#include "demo.h"
DemoObj :: DemoObj() : _rectSize(10), _growthDirection(1)
{
connect(&_timer, SIGNAL(timeout()), this, SLOT(AdvanceState()));
_timer.start(100); // 100 milliseconds delay per frame. You might want to put 2000 here instead
}
void DemoObj :: paintEvent(QPaintEvent * e)
{
QPainter p(this);
p.fillRect(rect(), Qt::white);
QRect r((width()/2)-_rectSize, (height()/2)-_rectSize, (_rectSize*2), (_rectSize*2));
p.fillRect(r, Qt::red);
}
void DemoObj :: AdvanceState()
{
_rectSize += _growthDirection;
if (_rectSize > 50) _growthDirection = -1;
if (_rectSize < 10) _growthDirection = 1;
update();
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
DemoObj obj;
obj.resize(150, 150);
obj.show();
return app.exec();
}

C++ and Qt - Problem with 2D graphics

Mission: Draw two lines with different color on one graph with automatic cliping, by adding points bit by bit.
So, what am I doing. Create class GraphWidget, inherited from QGraphicsView. Create member of QGraphicsScene. Create 2 QPainterPath instances, and add them to graphicsScene.
Then, I eventually call graphWidget.Redraw(), where call for QPainterPath.lineTo() for both instances. And I expect appearance of that lines of graphics view, but it doesn't.
I tired from reading Qt's doc and forums. What am I doing wrong?
We need to know more, what does not happen? Does the window appear at all? Are the lines not drawn? In the meantime try out this sample code if you want :) Edit: updated to show updating.
#include ...
class QUpdatingPathItem : public QGraphicsPathItem {
void advance(int phase) {
if (phase == 0)
return;
int x = abs(rand()) % 100;
int y = abs(rand()) % 100;
QPainterPath p = path();
p.lineTo(x, y);
setPath(p);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene s;
QGraphicsView v(&s);
QUpdatingPathItem item;
item.setPen(QPen(QColor("red")));
s.addItem(&item);
v.show();
QTimer *timer = new QTimer(&s);
timer->connect(timer, SIGNAL(timeout()), &s, SLOT(advance()));
timer->start(1000);
return a.exec();
}
You should get something like this:
The path in any QGraphicsPathItem can of course be updated later. You might want to keep the original painter path somewhere to avoid performance hit caused by all the path copying (I'm not sure if QPainterPath is implicitly shared...)
QPainterPath p = gPath.path();
p.lineTo(0, 42);
gPath.setPath(p);
Animation
It seems that you're trying to do some sort of animation/on-the-fly updating. There is entire framework for this in Qt. In the simplest form you can subclass QGraphicsPathItem, reimplement its advance() slot to automatically fetch next point from motion. The only thing left to do then would be calling s.advance() with the required frequency.
http://doc.trolltech.com/4.5/qgraphicsscene.html#advance
Evan Teran, sorry for that comment.
// Constructor:
GraphWidget::GraphWidget( QWidget *parent ) :
QGraphicsView(parent),
bounds(0, 0, 0, 0)
{
setScene(&scene);
QPen board_pen(QColor(255, 0, 0));
QPen nature_pen(QColor(0, 0, 255));
nature_path_item = scene.addPath( board_path, board_pen );
board_path_item = scene.addPath( nature_path, nature_pen );
}
// Eventually called func:
void GraphWidget::Redraw() {
if(motion) {
double nature[6];
double board[6];
// Get coords:
motion->getNature(nature);
motion->getBoard(board);
if(nature_path.elementCount() == 0) {
nature_path.moveTo( nature[0], nature[1] );
} else {
nature_path.lineTo( nature[0], nature[1] );
}
if(board_path.elementCount() == 0) {
board_path.moveTo( board[0], board[1] );
} else {
board_path.lineTo( board[0], board[1] );
}
}
}