I am trying to simulate mouse clicks on my widget using
QCoreApplication::sendEvent();
and it works fine for QPushButton and some other components.
For QSlider, (the Horizontal Slider in designer) I tried the below code which does not work.
QMouseEvent mousePressEvent(QEvent::MouseButtonPress,QPoint(50, 5), Qt::LeftButton, 0, 0);
QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mousePressEvent);
QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, QPoint(50, 5), Qt::LeftButton, 0, 0);
QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mouseReleaseEvent);
but with QTest it works fine.
QTest::mouseClick(ui->horizontalSlider, Qt::LeftButton, Qt::NoModifier, QPoint(50, 5));
Any suggestion for correcting same would be a great help. Thanks in advance.
Short answer would be the following:
// The following code should be changed
QMouseEvent mousePressEvent(QEvent::MouseButtonPress,QPoint(50, 5), Qt::LeftButton, 0, 0);
QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mousePressEvent);
QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, QPoint(50, 5), Qt::LeftButton, 0, 0);
QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mouseReleaseEvent);
// To the following one. Also, note that (QObject*) is not necessary, since QSlider is in the hierarchy with QObject, i.e. QObject is ancestor for the QSlider.
QMouseEvent mousePressEvent(QEvent::MouseButtonPress,QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(ui->horizontalSlider, &mousePressEvent);
QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(ui->horizontalSlider, &mouseReleaseEvent);
This would be the short answer. The longer version of it can be used by anyone who would like it is possible to deduce which events need to be used in order to simulate some sort of behavior on Qt elements.
First of all we need to investigate what type of event actually is introduced on QSlider when pressing mouse button on it. Then, we would be able to simulate it actually. In order to do that, we can use event filter. For simplicity, let's use this one at first:
class QSliderAnalyser
: public QObject
{
public:
QSliderAnalyser()
{
}
virtual ~QSliderAnalyser()
{
}
protected:
bool eventFilter(QObject* object, QEvent* event) override
{
qDebug() << event->type();
return QObject::eventFilter(object, event);
}
};
Then, we install this filter on your QSlider instance:
// Just keep in mind that memory is not managed in correct way here, and you should ensure proper cleanup of your elements on destruction.
ui->horizontalSlider->installEventFilter(new QSliderAnalyser());
After running an application, we can see lots of various of events. Example is below (after a couple of interactions with QSlider instance):
QEvent::Type(HoverMove)
QEvent::Type(HoverMove)
QEvent::Type(HoverMove)
QEvent::Type(HoverMove)
QEvent::Type(MouseButtonPress)
QEvent::Type(Paint)
QEvent::Type(MouseButtonRelease)
QEvent::Type(Paint)
QEvent::Type(HoverMove)
The most interesting part here is the following: QEvent::Type(MouseButtonPress) and QEvent::Type(MouseButtonRelease). In other words, QMouseEvent from different type is being simulated. Up to this point, you have probably understood yourself what to send, i.e. you already try to send correct events. However, maybe parameters are wrong? It might be so. So, let's take a look into QMouseEvent class constructor arguments:
QMouseEvent(QEvent::Type type, const QPointF &localPos, const QPointF &windowPos, const QPointF &screenPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, Qt::MouseEventSource source)
Based on constructor that has most arguments, we can deduce that event filter above could be rewritten a bit in the following way:
// Change "qDebug() << event->type();" to the following code.
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease)
{
auto mouseEvent = static_cast<QMouseEvent*>(event);
qDebug() << mouseEvent->type();
qDebug() << mouseEvent->localPos();
qDebug() << mouseEvent->windowPos();
qDebug() << mouseEvent->screenPos();
qDebug() << mouseEvent->button();
qDebug() << mouseEvent->buttons();
qDebug() << mouseEvent->modifiers();
qDebug() << mouseEvent->source();
}
After running the app again, on my device the following values are shown for mouse button press and release events accordingly:
QEvent::Type(MouseButtonPress)
QPointF(37,8)
QPointF(67,160)
QPointF(2747,550)
1
QFlags<Qt::MouseButtons>(LeftButton)
QFlags<Qt::KeyboardModifiers>(NoModifier)
Qt::MouseEventSource(MouseEventNotSynthesized)
QEvent::Type(MouseButtonRelease)
QPointF(37,8)
QPointF(67,160)
QPointF(2747,550)
1
QFlags<Qt::MouseButtons>(NoButton)
QFlags<Qt::KeyboardModifiers>(NoModifier)
Qt::MouseEventSource(MouseEventNotSynthesized)
So, what can be done here? I think we can just try simulating the event exactly as possible and just remove values one by one to see the minimum required event that would get processed as behavior wanted to be observed from Qt. So, initially the following code might be used:
QMouseEvent mousePressEvent(QEvent::MouseButtonPress, QPoint(37, 8), QPoint(67, 160), QPoint(2747, 550), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
QMouseEvent mouseReleaseEvent(QEvent::MouseButtonPress, QPoint(37, 8), QPoint(67, 160), QPoint(2747, 550), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, Qt::MouseEventNotSynthesized);
QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mousePressEvent);
QCoreApplication::sendEvent((QObject*)ui->horizontalSlider, &mouseReleaseEvent);
And after some observations, we can deduce the following:
Screen position is not required.
Window position is not needed, too.
Mouse event source is not needed, too.
You are not event obliged to use mouse button press AND release events. You can just either press or release your mouse. Qt will process even this type of behavior.
Basically, what is needed, is the following: Mouse event type (i.e. press and release), local position, button that has been pressed and released, and keyboard modifiers.
Thus, the code could be rewritten in the following ways and would be still working:
// Option One
QMouseEvent mousePressEvent(QEvent::MouseButtonPress, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QMouseEvent mouseReleaseEvent(QEvent::MouseButtonPress, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(ui->horizontalSlider, &mousePressEvent);
QCoreApplication::sendEvent(ui->horizontalSlider, &mouseReleaseEvent);
// Option Two
QMouseEvent mousePressEvent(QEvent::MouseButtonPress, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(ui->horizontalSlider, &mousePressEvent);
// Option Three
QMouseEvent mousePressEvent(QEvent::MouseButtonPress, QPoint(50, 5), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(ui->horizontalSlider, &mousePressEvent);
Thus, I have shown possibilities of how to deduce what type of events result in behavior that Qt processes on widgets. Hopefully, my answer was clear and helpful enough.
Related
I didn't find a proper solution to this problem, so I hope somebody can give me an answer to my problem:
I am using a normal QTreeWidget, but as items I use an own subclass of QTreeWidgetItem (because I needed to store some other information in the item). Now I want to use the itemClicked() signal by the QTreeWidget, but I think my slot doesn't get any signal and I think it has to do with the signature of itemClicked(), since it sends a QTreeWidgetItem and of course not my own subclass.
Is it possible that QTreeWidget doesn't detect a click on my own subclass items?
Here is my connection:
connect(treeWidget, SIGNAL(itemClicked(QTreeWidgetItem *)), this, SLOT(displayTransformData(QTreeWidgetItem*)));
And here is my slot:
void GUI::displayTransformData(QTreeWidgetItem* item) {
cout << "test" endl;
Q_actorTreeWidgetItem* actor_item = dynamic_cast<Q_actorTreeWidgetItem*>(item);
vtkSmartPointer<vtkActor> actor =
vtkSmartPointer<vtkActor>::New();
actor = actor_item->getActorReference();
double* position = new double[3];
position = actor->GetOrigin();
x_loc->setText(QString(std::to_string( position[0]).c_str() ));
}
I'm already trying to cast the item that I could get from the signal into my own subclass, but the slot function is never called, because the test from my cout line doesn't appear in the console.
I'm very grateful for every help!
The problem is your incorrect SIGNAL specification,
SIGNAL(itemClicked(QTreeWidgetItem *))
You should probably see a warning message at the console along the lines of:
QObject::connect: No such signal
tree_widget::itemClicked(QTreeWidgetItem *) in ...
From the documentation the actual signal spec is
void QTreeWidget::itemClicked(QTreeWidgetItem *item, int column)
So, using the old Qt4 syntax you need
connect(treeWidget, SIGNAL(itemClicked(QTreeWidgetItem *, int)),
this, SLOT(displayTransformData(QTreeWidgetItem*)));
If possible, however, you should make use of the newer Qt5 signal/slot syntax
connect(treeWidget, &QTreeWidget::itemClicked, this, &GUI::displayTransformData);
I've got a QTableWidget with some columns inside.
Due to my needs I set QComboBox inside some columns and fill them with necessary data.
void settingsDialog::onAddFieldButtonClicked()
{
fieldsTable->setRowCount(++rowCount);
combo = new QComboBox();
combo->addItem(QString("Choose from list..."));
foreach( int height, heightsAvailable)
combo->addItem(QString("%1").arg(height));
fieldsTable->setCellWidget(rowCount-1, 3, combo);
// etc for other columns ...
}
The question is how to catch signals from these combo boxes if they were changed?
I want to know row and col of changed widget (combo box) and the value which was set.
I've tried all available signals which are mentioned in Qt docs for QTableWidget, but they work only if cell doesn't have widget inside it.
Is there a simple and Qt-way to get what I need?
Instead of handling a signal from the table, you can handle currentIndexChanged signal from combo box itself.
QComboBox* combo = new QComboBox();
combo->addItem(QString("Choose from list..."));
combo->addItem(QString("first item"));
combo->setProperty("row", ui->tableWidget->rowCount() - 1);
combo->setProperty("column", 0);
connect(combo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(OnComboIndexChanged(const QString&)));
ui->tableWidget->setCellWidget(ui->tableWidget->rowCount() - 1, 0, combo);
And in the slot, you can use sender() to identify the combo box which emitted the signal.
void MainWindow::OnComboIndexChanged(const QString& text)
{
QComboBox* combo = qobject_cast<QComboBox*>(sender());
if (combo)
{
qDebug() << "row: " << combo->property("row").toInt();
qDebug() << "column: " << combo->property("column").toInt();
}
}
I have a widget containing a QSpinBox. This widget also has a QVector<int> Values. What I would like to do is get the QSpinBox to display values issued from Values only.
At first I thought that a new slot and signal in my widget would do the trick, something like
slot :
void ChangeSomeValue()
{
// QVector<int> Values;
// int Index;
int val = Values[ Index ];
emit( SomeValueChanged( val ) );
}
connects :
connect( UI->MySpinBox, SIGNAL( valueChanged(int) ), this, SLOT( ChangeSomeValue() ) );
connect( this, SIGNAL( SomeValueChanged(int ) ), UI->MySpinBox, SLOT( setValue(int) ) );
But then several problems arise :
QSpinBox emit another valueChanged after I call its setValue, resulting in an infinite loop (until my QVector explodes).
I still have to find a way to keep track of Index, depending on which of the QSpinBox arrows was clicked (up or down... I don't even think this is possible).
So my solution, with its problems, seems to be a dead end. Any idea?. I'd like to stick to QSpinBox, if possible.
Thanks !
[EDIT]
Subclassing :
class SpinBox : public QSpinBox
{
Q_OBJECT
public:
explicit SpinBox(const QVector<int> & values, QWidget * parent = 0) :
QSpinBox(parent),
mValues(values),
mIndex(0)
{
qSort(mValues);
setMinimum(mValues.at(0));
setMaximum(mValues.at(mValues.size() - 1));
setValue(mValues.at(0));
}
protected:
void stepBy(int steps) // re-implementaion
{
mIndex += steps;
mIndex = qBound(0, mIndex, mValues.size() - 1);
setValue(mValues.at(mIndex));
}
private:
QVector<int> mValues;
int mIndex;
};
I Would suggest writing your own class to do it by sub-classing QAbstractSpinBox.
Maybe take a look at the accepted answer on this question:
How to subclass QSpinBox so it could have int64 values as maxium and minimum
QSpinBox emit another valueChanged after I call its setValue, resulting in an infinite loop (until my QVector explodes).
You can prevent this by using QObject::blockSignals(). Make sure to unblock signals afterwards.
I still have to find a way to keep track of Index, depending on which of the QSpinBox arrows was clicked (up or down... I don't even think this is possible).
Well, I suppose you can store the old value in a member variable, and when QSpinBox emits valueChanged(), you can compare the new to the old value to figure out whether the up or the down arrow was pressed.
That said, I don't know if that's enough to make QSpinBox behave like you want, correcting the value after it was changed once is a bit hacky. Subclassing QAbstractSpinbox might be better indeed.
After some trouble I've managed to correctly render to texture inside a Frame Buffer Object in a Qt 4.8 application: I can open an OpenGL context with a QGLWidget, render to a FBO, and use this one as a texture.
Now I need to display the texture rendered in a QPixmap and show it in some other widget in the gui. But.. nothing is shown.
Those are some pieces of code:
// generate texture, FBO, RBO in the initializeGL
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glGenFramebuffers(1, &fboId);
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glGenRenderbuffers(1, &rboId);
glBindRenderbuffer(GL_RENDERBUFFER, rboId);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// now in paintGL
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
// .... render into texture code ....
if(showTextureInWidget==false) {
showTextureInWidget = true;
char *pixels;
pixels = new char[TEXTURE_WIDTH * TEXTURE_HEIGHT * 4];
glReadPixels(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, pixels);
QPixmap qp = QPixmap(pixels);
QLabel *l = new QLabel();
// /* TEST */ l->setText(QString::fromStdString("dudee"));
l->setPixmap(qp);
QWidget *d = new QWidget;
l->setParent(d);
d->show();
}
glBindFramebuffer(GL_FRAMEBUFFER, 0); // unbind
// now draw the scene with the rendered texture
I see the Widget opened but.. there is nothing inside it. If I decomment the test line.. I see the "dudee" string so I know that there is a qlabel but.. no image from the QPixmap.
I know that the original data are ´unsigned char´ and I'm using ´char´ and I've tried with some different color parameters (´GL_RGBA´, ´GL_RGB´ etc) but I don't think this is the point.. the point is that I don't see anything..
Any advice? If I have to post more code I will do it!
Edit:
I haven't posted all the code, but the fact I'd like to be clear is that the texture is correctly rendered as a texture inside a cube. I'm just not able to put it back in the cpu from gpu
Edit 2:
Thanks to the peppe answer I found out the problem: I needed a Qt object that accept as a constructor some raw pixels data. Here is the complete snippet:
uchar *pixels;
pixels = new uchar[TEXTURE_WIDTH * TEXTURE_HEIGHT * 4];
for(int i=0; i < (TEXTURE_WIDTH * TEXTURE_HEIGHT * 4) ; i++ ) {
pixels[i] = 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glReadPixels( 0,0, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
qi = QImage(pixels, TEXTURE_WIDTH, TEXTURE_HEIGHT, QImage::Format_ARGB32);
qi = qi.rgbSwapped();
QLabel *l = new QLabel();
l->setPixmap(QPixmap::fromImage(qi));
QWidget *d = new QWidget;
l->setParent(d);
d->show();
Given that that's not all of your code and -- as you say -- the texture is correctly filled, then there's a little mistake going on here:
glReadPixels(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, pixels);
QPixmap qp = QPixmap(pixels);
The QPixmap(const char *) ctor wants a XPM image, not raw pixels. You need to use one of the QImage ctors to create a valid QImage. (You can also pass ownership to the QImage, solving the fact that you're currently leaking pixels...)
Once you do that, you'll figure out that
the image is flipped vertically, as OpenGL has the origin in the bottom left corner, growing upwards/rightwards, while Qt assumes origin in the top left, growing to downwards/rightwards;
the channels might be swapped -- i.e. OpenGL is returning data with the wrong endianess. I don't remember in this case if using glPixelStorei(GL_PACK_SWAP_BYTES) or GL_UNSIGNED_INT_8_8_8_8 as the type may help, eventually you need to resort to a CPU-side loop to fix your pixel data :)
I have MyWindow class which pops up a blank window, which accepts a mouse click, I need to unit test the mouse click event
Code snippet:
void TestGui::testGUI_data()
{
QTest::addColumn<QTestEventList>("events");
QTest::addColumn<QTestEventList>("expected");
Mywindow mywindow;
QSize editWidgetSize = mywindow.size();
QPoint clickPoint(editWidgetSize.rwidth()-2, editWidgetSize.rheight()-2);
QTestEventList events, expected;
events.addMouseClick( Qt::LeftButton, 0, clickPoint);
expected.addMouseClick( Qt::LeftButton, 0, clickPoint);
QTest::newRow("mouseclick") << events << expected ;
}
void TestGui::testGUI()
{
QFETCH(QTestEventList, events);
QFETCH(QTestEventList, expected);
Mywindow mywindow;
mywindow.show();
events.simulate(&mywindow);
QCOMPARE(events, expected); } // prints FAIL! : TestGui::testGUI(mouseclick) Compared values are not the same
...
}
How to test the mouse click on mywindow. is there any better approach to unit test mouse events?
Thanks,
vels
Take a look on QTest namespace documentation. There is QTest::mouseClick function which allows you to emulate mouse click on the given position of your widget (there is even no necessity to show the widget).
As far as I remember QCOMPARE uses "operator==" for the arguments given and if it returns false then it tries to print it using "QTest::toString<T>(const T& t)" function. If there is no implementation for a type of QCOMPARE arguments then it will just print that values are different. Again take a look on the QTest namespace documentation for details how to reimplement "QTest::toString<T> (const T& t)" for the type you need.