QStateMachine - QMouseEvent - c++

In another question you tell me to use QStateMachine.
I'm new to Qt and it's the first time i use the objects so I make a lot of logical mistake, so using QStateMachine it's a big problem...
It's the only way to do thath ? I try to explain my program:
I want to create a card's game and in the previous version I've used an old graphics library with this sequence of commands:
-> print cards on the scene
-> wait for a mouse input (with a do-while)
-> if(isMouseClick(WM_LBUTTONDOWN))
-> if(mouse position is on the first card)
-> select that card. So i wish to do the same thing with QGraphics.
In this way I tell the program:
-> print cards
-> wait for a mouse event
-> print the card that I've selected with that event.
Now I want to change the program graphics and I've introduced QGraphics.
I've created a scene and print all the objects "card" on it so now i want to tell the program:
-> print the object and wait the mouse input
-> if a card is to selected with the left clik
-> print that card in scene, wait 1/2 second and go ahead with the program
The problem is that I use a for 1 to 20 (I must run that 20 times in a match).
I've tried to lauch the program with a random G1 and COM play but the application freeze until the last execution of the for and I print on the scene only the last configuration of cards.
That is the reason because previously I said I want the program to stop...
It is possible to do without QStateMachine ?
Simply telling him: "pause", print this situation, wait for mouse and go ahead ?

The below is a complete example, 71 lines long, presented in the literate programming style. It is also available on github. The example consists of a qmake .pro file, not shown, and main.cpp, shown in the entirety below. The example has the following structure:
Header
Card Item
State Machine Behaviors
Main
Footer
Main
First, let's set up our scene:
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QGraphicsScene scene;
QGraphicsView view{&scene};
scene.addItem(new CardItem(0, 0, "A"));
scene.addItem(new CardItem(20, 0, "B"));
The state machine has three states:
QStateMachine machine;
QState s_idle{&machine}; // idle - no card selected
QState s_selected{&machine}; // card selected, waiting 1/2 second
QState s_ready{&machine}; // ready with card selected
machine.setInitialState(&s_idle);
We'll use helper functions to declaratively add behaviors to the machine. This isn't the only possible pattern, but it works and is fairly easy to apply. First, when any items are selected, the state changes from s_idle to s_selected:
on_selected(&s_idle, &scene, true, &s_selected);
Then, after a timeout, the state changes to s_ready:
on_delay(&s_selected, 500, &s_ready);
If the items are deselected, we go back to s_idle:
on_selected(&s_selected, &scene, false, &s_idle);
on_selected(&s_ready, &scene, false, &s_idle);
Since we don't have much better to do, we can simply deselect all items once the s_ready state has been entered. This makes it clear that the state was entered. Of course, it'll be immediately left since the selection is cleared, and we indicated above that s_idle is the state to be when no items are selected.
QObject::connect(&s_ready, &QState::entered, &scene, &QGraphicsScene::clearSelection);
We can now start the machine and run our application:
machine.start();
view.show();
return app.exec();
}
Note the minimal use of explicit dynamic memory allocation, and no manual memory management whatsoever.
Card Item
The CardItem class is a simple card graphics item. The item is selectable. It could also be movable. The interaction is handled automatically by the graphics view framework: you don't have to deal with interpreting mouse presses/drags/releases manually - at least not yet.
class CardItem : public QGraphicsObject {
Q_OBJECT
const QRect cardRect { 0, 0, 80, 120 };
QString m_text;
QRectF boundingRect() const Q_DECL_OVERRIDE { return cardRect; }
void paint(QPainter * p, const QStyleOptionGraphicsItem*, QWidget*) {
p->setRenderHint(QPainter::Antialiasing);
p->setPen(Qt::black);
p->setBrush(isSelected() ? Qt::gray : Qt::white);
p->drawRoundRect(cardRect.adjusted(0, 0, -1, -1), 10, 10);
p->setFont(QFont("Helvetica", 20));
p->drawText(cardRect.adjusted(3,3,-3,-3), m_text);
}
public:
CardItem(qreal x, qreal y, const QString & text) : m_text(text) {
moveBy(x, y);
setFlags(QGraphicsItem::ItemIsSelectable);
}
};
State Machine Behaviors
It is helpful to factor out the state machine behaviors into functions that can be used to declare the behaviors on a given state.
First, the delay - once the src state is entered, and a given number of millisconds elapses, the machine transitions to the destination state:
void on_delay(QState * src, int ms, QAbstractState * dst) {
auto timer = new QTimer(src);
timer->setSingleShot(true);
timer->setInterval(ms);
QObject::connect(src, &QState::entered, timer, static_cast<void (QTimer::*)()>(&QTimer::start));
QObject::connect(src, &QState::exited, timer, &QTimer::stop);
src->addTransition(timer, SIGNAL(timeout()), dst);
}
To intercept the selection signals, we'll need a helper class that emits a generic signal:
class SignalSource : public QObject {
Q_OBJECT
public:
Q_SIGNAL void sig();
SignalSource(QObject * parent = Q_NULLPTR) : QObject(parent) {}
};
We then leverage such universal signal source to describe the behavior of transitioning to the destination state when the given scene has a selection iff selected is true, or has no selection iff selected is false:
void on_selected(QState * src, QGraphicsScene * scene, bool selected, QAbstractState * dst) {
auto signalSource = new SignalSource(src);
QObject::connect(scene, &QGraphicsScene::selectionChanged, signalSource, [=] {
if (scene->selectedItems().isEmpty() == !selected) emit signalSource->sig();
});
src->addTransition(signalSource, SIGNAL(sig()), dst);
}
Header and Footer
The example begins with the following header:
// https://github.com/KubaO/stackoverflown/tree/master/questions/sm-cards-37656060
#include <QtWidgets>
It ends with the following footer, consisting of moc-generated implementations of the signals and object metadata for the SignalSource class.
#include "main.moc"

In qt you don't need to actively wait for an event (and usually shouldn't). Just subclass the event handling method of a widget which is part of the main interface.
For instance this is the code which use a subclass of a QGraphicsItem to change the game state. You could do the same with the scene itself, widgets, etc... but it should usually be like this.
void CardGameGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
if(event->button() == Qt::RightButton)
{
makeQuickChangesToGameState();
scene()->update(); //ask for a deffered ui update
}
QGraphicsItem::mousePressEvent(event);
}
even if you are somehow using a state machine, makeQuickChangesToGameState() should just trigger the machine state change, and go back asap.

Related

Qt adding child widget in resizeEvent

I have a widget W deriving from QFrame with layout set to an instance of QVBoxLayout. I wonder if the following resizeEvent implementation is correct or is it going to cause an infinite loop:
void W::resizeEvent(QResizeEvent *event) {
for (/* some condition based on the new size of this widget */) {
// Infinite loop or not?
qobject_cast<QVBoxLayout *>(layout())->addWidget(new QWidget());
}
}
So far it worked for me, is this by pure luck?
This is okay. W owns a QLayout which owns QWidget. Adding the QWidget to the QLayout does not change the size of W. You see this all the time. For example, if you place a child widget in a parent and the parent is too small, the child widget will be clipped. Stately differently, the size of the parent does not stretch to accommodate the size of the child. I believe your code would be a typical way to hide or show widgets based on the size of the parent (for example, when the window size changes).
Painting and constructing a hierarchy of widgets are two different things. So, adding QWidgets is just fine, but using QPainter directly in resizeEvent not.
Hierarchy of QWidgets
A hierarchy of QWidgets derivatives (QLineEdit, QPushButton, ...) is a high level specification of how the graphical user interface should look like and may be ordered using QLayout items.
Painting
Painting (using QPainter) is the process of actually drawing something on the screen and is purely done in the virtual function QWidget::paintEvent. Every derivative of QWidget should provide an implementation of this empty base function. The default derivatives (QLineEdit, ...) provide an implementation of paintEvent based on their current state (size of the widget, current text for a QLineEdit, ...) and the current QStyle object, which is typically automatically set based on your OS, but may be changed programmatically using QWidget::setStyle or QApplication::setStyle. A repaint can be requested using QWidget::update.
"Should not/need not" vs "may not"
The sentence "No drawing need be (or should be) done inside this handler." is meant for people implementing a custom QWidget (with a new implementation of paintEvent) to make it clear that you should not implement your painting here, but that a paintEvent will be automatically triggered.
"Should not/need not" is some advice, they do not write "may not". So, if you for some reason (ex. real-time applications) want an immediate screen refreshment, you may invoke a repaint immediately using repaint, resulting in paintEvent being called during resizeEvent. As long as all the QPainter operations on a QWidget are inside a paintEvent (as required by the warning in the QPainter documentation), everything is just fine.
Adding widgets to the layout, using addWidget, within the resizeEvent function is not a problem as it does not instantly trigger a drawing.
You can easily verify this by compiling and executing this simple project:
dialog.h:
#pragma once
#include <QDialog>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
void resizeEvent(QResizeEvent *event);
void paintEvent(QPaintEvent *event);
private:
bool resizing;
};
dialog.cpp:
#include "dialog.h"
#include <QResizeEvent>
#include <QVBoxLayout>
#include <QPushButton>
#include <QDebug>
Dialog::Dialog(QWidget *parent)
: QDialog(parent),
resizing(false)
{
new QVBoxLayout(this);
}
Dialog::~Dialog()
{
}
void Dialog::resizeEvent(QResizeEvent *event)
{
resizing = true;
if ( event->size().width() == event->size().height() )
{
qDebug() << "Adding widget";
// Infinite loop or not?
layout()->addWidget(new QPushButton());
}
resizing = false;
}
void Dialog::paintEvent(QPaintEvent *event)
{
if ( resizing )
{
qDebug() << "Painting while resizing widget";
}
}
main.cpp:
#include "dialog.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.show();
return a.exec();
}
When you run the program, resize the dialog to make it be square (width==height), some buttons are inserted ("Adding widget" is printed to the console), but you'll never see "Painting while resizing widget" message. This is most likely because addWidget sets a dirty display flag that is processed later by the framework. It invalidates the display, but does not repaint it right away.
So what you are doing is fine and does not violate the framework requirement ("No drawing need be (or should be) done inside this handler.").
However, if you are not confident (maybe the painting could be operated right away on different OS, or in future Qt versions....you can't be sure), you can also delay the insertion by emitting a signal connected to a slot using Qt::QueuedConnection, this slot would be executed "later" and then do the call to addWidget, guaranteeing that it's done outside the resizeEvent function.

QGraphicsItem disappears when calling setPos from a different thread

I have two types of ­­­­­­­­­­QGraphicsItem­­­­­­s on a QGraphicsView, one of those two types are in the scene like grid with z-index 1, the other ones, ants, are on top of them with z-index 2. When starting the program, I set all ants to position 0,0 and add them to the scene. But then I start moving those ants from another Thread by calling setPos() on them - and then my computer eats the ants! They disapper at their old position, but don't appear at their new position. The new position is inside the scene.
Here is the code of Ant class (that inherits QGraphicsItem):
#include "ant.h"
#include "constants.h"
#include <QPainter>
Ant::Ant()
{
setZValue(2);
}
QRectF Ant::boundingRect() const
{
return QRect(QPoint(0,0), G_FIELD_RECT_SIZE);
}
void Ant::paint(QPainter *painter, const QStyleOptionGraphicsItem *item, QWidget *widget)
{
Q_UNUSED(item)
Q_UNUSED(widget)
QBrush b = painter->brush();
if (food())
painter->setBrush(Qt::blue);
else
painter->setBrush(Qt::black);
painter->drawEllipse(2,3, G_FIELD_RECT_WIDTH - 4, G_FIELD_RECT_HEIGHT - 6);
painter->setBrush(b);
}
After a bit more testing, I found out that everything is working as long as I call setPos from the Qt Event Thread. As soon as I call it in an custom thread, the ants disappear. Any idea how I can solve this?
You must go back to the main thread to do the setPos. As commented by ddriver, you should not modify GUI from a thread (you usually get qDebug messages when doing so, didn't you get any in your debugger window?).
You just need to:
Add a new signal to your Ant class (like signalSetPos( QPoint pos ))
Add a new slot to your Ant class (like doSetPos( QPoint pos )). This slot implementation simply calls setPos(pos).
Connect them using Qt::QueuedConnection or Qt::BlockingQueuedConnection (fifth parameter of the connect function, for GUI update, Qt::QueuedConnection may be preferable because it won't block your thread).
From the thread where you used to do setPos( newPos ), just do emit signalSetPos( newPos ). Then, doSetPos will be executed (later if you used Qt::QueuedConnection, right away if you used Qt::BlockingQueuedConnection) from the main thread.
Check this post for more information about emitting signals from threads:
Qt - emit a signal from a c++ thread

Qt mouseReleaseEvent() not trigggered?

I got a library to display pictures, lets call it PictureGLWidget, with:
class PictureGLWidget: public QGLWidget {
so PictureGLWidget extends QGLWidget. In PictureGlWidget the
void PictureGlWidget::mouseReleaseEvent(QMouseEvent* releaseEvent);
is already implemented.
I started an own project, lets say class MyMainWindow, where I just use a PictureGlWidget as a Pointerobject:
PictureGlWidget * myPictureGLWidget = new PictureGlWidget(...);
//..
layout->addWidget(myPictureGLWidget , 0, 1);
Here at this point, I already can see the PictureGlWidget and the corresponding picture in my MainwindowWidget. When I click in that PictureGlWidget, hold the mouse, I can move the picture (like 2D-scrolling), since it is much bigge than my little MainWindow.
Further on PictureGlWidget provides a function
bool PictureGlWidget::getPictureLocation(double& xPos, double& yPos);
which just tells me the Pictures center position, where I released the current clipping of the picture. Remeber my picture is much bigger than my little MainWindowWidget and thus much much more bigger than my PictureGLWidget. Imagine the picture has 4000x4000px (0,0 upper left). The PictureGLWidget is only to display lets say 800x800px. So the getPictureLocation() sets the center cooridinates of the current displayed picture part and it would return something like (400, 400), which might be somewhere in the midldle upper left corner.
I would like to grab the current displayed pictureparts (just a little part of that big picture) center position, after scrolling in that Widget and I released the mouse. I thought I do that by overwriting the
MyMainWindow::mouseReleaseEvent(QMouseEvent *event){ qDebug() << "Mouse released!"; }
method, but did not connected it anywhere yet. Currently it is not reacting on my mouseReleases and that text is not displayed.
The virtual protected methods in QWidget that you can override to react on some events don't need to be "connected". These are not Qt slots but classical functions Qt automatically calls when necessary.
As explained in Qt Event system doc, if the implementation PictureGlWidget::mouseReleaseEvent(QMouseEvent*) accept the event, it is not propagated to the parent widget. But you can install an event filter to your PictureGLWidget and receive events before they are sent to it.
PictureGlWidget * myPictureGLWidget = new PictureGlWidget(...);
layout->addWidget(myPictureGLWidget , 0, 1);
myPictureGLWidget->installEventFilter(this);
Then implements the right method in your main window:
bool MyMainWindow::eventFilter(QObject *object, QEvent *event)
{
if (object == myPictureGLWidget && event->type() == QEvent::MouseButtonRelease) {
QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
// Do what you need here
}
// The event will be correctly sent to the widget
return false;
// If you want to stop the event propagation now:
// return true
}
You can even decide if, after doing what you have to do, you want to stop the event, or send it to the PictureQLWidget instace (the normal behavior).
Doc:
http://doc.qt.io/qt-4.8/qobject.html#installEventFilter
http://doc.qt.io/qt-4.8/qobject.html#eventFilter
Do not forget the Q_OBJECT keyword in your MyGLwidget custom class declaration

Using QPainter when every time I receive some data

I am a beginner in Qt, and I want to use QPainter.
My process is like this: I receive data coordinates (x,y) from the serial port, like (1,1), (2,3), etc. I want to draw these points in a window every time I receive data.
I see the QPainter is used in events, and just paints one time. How can I use it every time I receive data? Just like a have a signal DataCome() and a slot Paint().\
By the Way ,thx a lot to the Answer.Your advise is very Useful .
In short ,updata() or repaint() is work in this case .
I have another question .
Assume ,the serial port continuous to send the coordinate points to computer,
and I want to display all the point in the window. Is there some method ,I can leave those points came early on the window,and I just need to paint the new points?Like "hold on " in matlab. Or I need a container to store the coordinates ,and paint all of them very time.
I've set a quick example that will hopefully help you understand the mechanisms you need to utilize to accomplish your task.
It consists of a Listener class which listens for data and sends it to the Widget for drawing. In my example I've set it it up so that the data is randomly generated and sent on regular intervals using a timer, but in your case that will be your serial port data.
Since I assume what you want to do is a plot, you cannot use the paintEvent to draw single points, because each time it will show only one point and the points data will not accumulate, so you need to draw to a pixmap, which you just display in the paintEvent.
Here are the Widget and Listener classes:
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget *parent = 0) : QWidget(parent) {
resize(200, 200);
p = new QPixmap(200, 200);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.drawPixmap(0, 0, 200, 200, *p);
}
public slots:
void receiveData(int x, int y) {
QPainter painter(p);
painter.setBrush(Qt::black);
QPoint point(x, y);
painter.drawPoint(point);
data.append(point);
repaint();
}
private:
QPixmap *p;
QVector<QPoint> data;
};
class Listener : public QObject {
Q_OBJECT
public:
Listener(QObject *p = 0) : QObject(p) {
QTimer * t = new QTimer(this);
t->setInterval(200);
connect(t, SIGNAL(timeout()), this, SLOT(sendData()));
t->start();
}
signals:
void dataAvaiable(int, int);
public slots:
void sendData() {
emit dataAvaiable(qrand() % 200, qrand() % 200);
}
};
... and main:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
Listener l;
QObject::connect(&l, SIGNAL(dataAvaiable(int,int)), &w, SLOT(receiveData(int,int)));
w.show();
return a.exec();
}
So what happens is a random data will be generated every 200 msec, sent to the Widget, where it is added to the pixmap and the Widget is updated to show the new entry.
EDIT: Considering how small a point (pixel) is, you may want to draw small circles instead. You can also color the point based on its data values, so you can get a gradient, for example low values might be green, but the higher it gets it can turn yellow and finally red...
You also might want to add the received data to a QVector<QPoint> if you will need it later, this can be done in the receiveData slot.
Another thing that might be worth mentioning - in the example everything is in range 0-200, the data, the plot window - very convenient. In reality this won't be the case, so you will need to map the data to the plot size, which may be changing depending on the widget size.
Here is a template I commonly use to normalize values in some range. You may want to simplify it a bit depending on your requirements.
template <typename Source, typename Target>
Target normalize(Source s, Source max, Source min, Target floor, Target ceiling) {
return ((ceiling - floor) * (s - min) / (max - min) + floor);
}
Edit2: Added the data vector to store all the received points in numerical form.
QPainter can operate on any object that inherits from QPaintDevice.
One such object is QWidget. When one wants QWidget to re-render, you call repaint or update with the rectangular region that requires re-rendering.
repaint immediately causes the paintEvent to happen, whilst update posts a paintEvent on the event queue. Both these are slots, so it should be safe to hook them up to a signal from another thread.
Then you have to override the virtual method "paintEvent" and create a painter with the widget:
void MyWidget::paintEvent( QPaintEvent * evt )
{
QPainter painter( this );
//... do painting using painter.
}
You can look at the AnalogClock example that is distributed with Qt Help as example.
You use QPainter only in the paintEvent of a QWidget. You can do it like this:
Keep a list of received points as a member and in the paintEvent, traverse this list and paint the required points. When a new point is received, add it to the list and call widget->update(). This tells the widget to refresh itself, and the widget will call paintEvent when the time is right.
Create a QPixmap instance, then draw on that like this:
QPixmap pixmap(100, 100);
QPainter p(&pixmap);
// do some drawing
You can then do with the pixmap whatever you want: paint it in the paint event, write it to disk...

Draw pixel based graphics to a QWidget

I have an application which needs to draw on a pixel by pixel basis at a specified frame rate (simulating an old machine). One caveat is that the main machine engine runs in a background thread in order to ensure that the UI remains responsive and usable during simulation.
Currently, I am toying with using something like this:
class QVideo : public QWidget {
public:
QVideo(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {
}
void draw_frame(void *data) {
// render data into screen_image_
}
void start_frame() {
// do any pre-rendering prep work that needs to be done before
// each frame
}
void end_frame() {
update(); // force a paint event
}
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.drawImage(rect(), screen_image_, screen_image_.rect());
}
QImage screen_image_;
};
This is mostly effective, and surprisingly not very slow. However, there is an issue. The update function schedules a paintEvent, it may not hapen right away. In fact, a bunch of paintEvent's may get "combined" according to the Qt documentation.
The negative effect that I am seeing is that after a few minutes of simulation, the screen stops updating (image appears frozen though simulation is still running) until I do something that forces a screen update for example switching the window in and out of maximized.
I have experimented with using QTimer's and other similar mechanism to have the effect of the rendering being in the GUI thread so that I can force immediate updates, but this resulted in unacceptable performance issues.
Is there a better way to draw pixels onto a widget constantly at a fixed interval. Pure Qt solutions are preferred.
EDIT: Since some people choose to have an attitude instead of reading the whole question, I will clarify the issue. I cannot use QWidget::repaint because it has a limitation in that it must be called from the same thread as the event loop. Otherwise, no update occurs and instead I get qDebug messages such as these:
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
QPainter::begin: A paint device can only be painted by one painter at a time.
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent
EDIT: to demonstrate the issue I have created this simple example code:
QVideo.h
#include <QWidget>
#include <QPainter>
class QVideo : public QWidget {
Q_OBJECT;
public:
QVideo(QWidget *parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {
}
void draw_frame(void *data) {
// render data into screen_image_
// I am using fill here, but in the real thing I am rendering
// on a pixel by pixel basis
screen_image_.fill(rand());
}
void start_frame() {
// do any pre-rendering prep work that needs to be done before
// each frame
}
void end_frame() {
//update(); // force a paint event
repaint();
}
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.drawImage(rect(), screen_image_, screen_image_.rect());
}
QImage screen_image_;
};
main.cc:
#include <QApplication>
#include <QThread>
#include <cstdio>
#include "QVideo.h"
struct Thread : public QThread {
Thread(QVideo *v) : v_(v) {
}
void run() {
while(1) {
v_->start_frame();
v_->draw_frame(0); // contents doesn't matter for this example
v_->end_frame();
QThread::sleep(1);
}
}
QVideo *v_;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QVideo w;
w.show();
Thread t(&w);
t.start();
return app.exec();
}
I am definitely willing to explore options which don't use a temporary QImage to render. It is just the only class in Qt which seems to have a direct pixel writing interface.
Try emitting a signal from the thread to a slot in the event loop widget that calls repaint(), which will then execute right away. I am doing something like this in my graphing program, which executes the main calculations in one thread, then tells the widget when it is time to repaint() the data.
In similar cases what I did was still using a QTimer, but doing several simulation steps instead of just one. You can even make the program auto-tuning the number of simulation steps to be able to get whatever frames per seconds you like for the screen update.