QGuiApplication::commitDataRequest: QML window is not painted - qt

I'm trying to add quit confirmation message at OS shutdown according to this example:
https://doc.qt.io/qt-5/qsessionmanager.html#allowsInteraction
I interact with the user via QML interface. It is asynchronous, so I use slots/signals. And I use additional QEventLoop to stay inside of QGuiApplication::commitDataRequest call while interacting with the user.
The trouble is that after I shutdown OS (Windows 10) - my app prevents OS to shutdown and it's good, but its screen is just white, or contains old state without the confirmation dialog. I need to hide/restore window or change its size, or do some mouse clicks inside of it to force it repaint. When it repaint all is OK and my confirmation dialog is there.
Is this a bug? Is there a workaround?
This is the code I use:
void AppQuitConfirmationAtOsShutdownManager::onCommitDataRequest(
QSessionManager &m)
{
if (!m_ui->isConfirmationRequired())
return;
if (!m.allowsInteraction())
return;
bool isConfirmed = true;
QEventLoop loop;
qtconnect(m_ui.data(), &AppQuitConfirmationUiManager::confirmationResult,
&loop, [&](bool confirmed)
{
isConfirmed = confirmed;
loop.quit();
},
Qt::QueuedConnection);
m_ui->requestConfirmation();
loop.exec();
m.release();
if (!isConfirmed)
m.cancel();
}

Related

Accessibility notification in Qt

I am trying to push notification to Jaws or NVDA when certain events occur.
These events can occur any time and any application widget can have the focus. They are not linked to user actions but to the controller events.
This is my try:
void announceNewMessageIfNeeded(){
if(QAccessible::isActive()){
QWidget* focusWidget = QApplication::focusWidget();
if(focusWidget != nullptr){
auto* accessibleInterface = QAccessible::queryAccessibleInterface(focusWidget);
accessibleInterface->setText(QAccessible::Name, "New Message");
auto *ev = new QAccessibleEvent(accessibleInterface, QAccessible::Alert);
QAccessible::updateAccessibility(ev);
}
}
}
I tried the above code with various little changes but I either do not have accesibility update or undesired access to nullpointers.
With debug logs, I know for sure that the focusWidget is correct (it points to the item having the currentFocus) and that announceNewMessageIfNeeded is called.
Any idea?
solution that seems to work:
if(QAccessible::isActive()){
QWidget* focusedWidget = QApplication::focusWidget();
if(focusedWidget != nullptr){
auto *ev = new QAccessibleValueChangeEvent(focusedWidget, "New Message");
QAccessible::updateAccessibility(ev);
}
}

How do i connect messageBox when it is clicked, to a slot?

I want to create a messagebox asking question whether a user want to play again or not. When user clicks either a button , it performs a task. The task is defined in a slot. How can i connect the button click to that slot??
QMessageBox::StandardButton reply=QMessageBox::question(this,"GAME Over-Do you want to play again?");
connect(QMessageBox,SIGNAL(buttonClicked()),this,SLOT(box());
it shows QMessageBox is a class, and is unable to connect it to that slot. I want to connect to that slot.
There is different ways to use QMessageBox. You could use blocking static functions of QMessageBox and check response like that:
QMessageBox::StandardButton reply = QMessageBox::question(this,"Title", "GAME Over-Do you want to play again?");
if(reply == QMessageBox::Yes)
{
//call your slot
//box();
qDebug() << " Yes clicked";
}
else
{
//Game over
qDebug() << "game over";
}
but this will block execution of your code until user clicks some button in message box.
If you need your code run forward without waiting for user response you could use QMessageBox in non-blocking way:
QMessageBox * msg = new QMessageBox(QMessageBox::Question, "Title", "GAME Over-Do you want to play again?", QMessageBox::Yes| QMessageBox::No, this);
connect(msg,SIGNAL(accepted()),this,SLOT(box()));
connect(msg,SIGNAL(rejected()),this,SLOT(gameover()));
msg->show();
qDebug() << "Not blocked";

How can I simulate mouse clicks by posting events to the Qt event system?

I would like to do a rudimentary automation test of my Qt application. It records mouse events and writes them to a file (f.e. mousepress(300, 400)). When starting the automation, it reads the coordinates from the file, sends the appropriate mouse events and would do a pixel comparison with a previously saved screenshot.
Currently, I have an overlay widget that spans the app and has transparent mouse events. All it does is track the coordinates.
When reading the data back in, that overlay paints a rectangle on the mouse press location.
I need help when sending mousePressEvents to Qt's event system. It draws the points on the correct location but never actually does the physical click.
Is there a way to do this with Qt or would I have to use Window's SendInput()?
Is there a way to pause and wait until the mouse event has finished? I would need to know when the event is complete in order to start the pixel by pixel comparison.
Widget::Widget( QWidget *parent )
: QFrame( parent )
, mPoint(QPoint(0,0))
{
setWindowFlags(Qt::WindowStaysOnTopHint);
setStyleSheet("background-color: rgba(0, 0,255, 2%);");
setAttribute(Qt::WA_TransparentForMouseEvents, true);
setGeometry(parent->geometry());
...
}
void Widget::run()
{
QFile file( "coordinates.txt", NULL );
if(!file.open( QIODevice::ReadOnly ))
return;
QTextStream in(&file);
int i = 0;
while (!in.atEnd())
{
QString line = in.readLine();
if(line.startsWith("mousepress"))
{
int startIndex = line.indexOf('(');
int endIndex = line.indexOf(')');
QString coord = line.mid(startIndex+1, line.size() - startIndex - 2);
QStringList nbr = coord.split(',');
mPoint = QPoint(nbr[0].toInt(), nbr[1].toInt());
QWidget *receiver = QApplication::widgetAt(mPoint);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, mPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::postEvent(receiver, event); // same result with sendEvent()
QCoreApplication::processEvents();
update();
// wait till the event finished, then continue with next point
}
}
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter p( this );
QPen pen;
pen.setBrush(Qt::NoBrush);
if(!mPoint.isNull())
{
pen.setColor(Qt::red);
pen.setWidth( 2 );
p.setPen(pen);
p.drawRoundRect(mPoint.x(), mPoint.y(), 10, 10, 25, 25);
p.drawText(mPoint, QString::number(mPoint.x()) + ", " + QString::number(mPoint.y()));
}
}
[Edited]
I followed ddriver's suggestion and it works after a few changes: I save global and local positions in the file, to send to the QMouseEvent.
How could I be sure that the mouse click is complete before doing a screenshot and comparing it to a saved image?
void Widget::DoStep()
{
if(!mInStream.atEnd())
{
QString line = mInStream.readLine();
if(line.startsWith("MouseButtonPress"))
{
QPoint pos = parseGlobalPos();
QPoint localPos = parseLocalPos();
QWidget *receiver = QApplication::widgetAt(pos);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress,localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, event);
QMouseEvent *eventRelease = new QMouseEvent(QEvent::MouseButtonRelease, localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, eventRelease);
// after successful click, take screenshot and compare them
}
}
if (!mInStream.atEnd())
QMetaObject::invokeMethod(this, "DoStep", Qt::QueuedConnection);
else
file.close();
}
If I understand the problem correctly, its source is the blocking while loop, which blocks the thread and doesn't allow the event loop to spin and process events. There is no way to "pause" as that would block the event loop as well, and it wouldn't be able to do the work either, but there is a way to split the work to be done one step at a time, one event loop cycle at a time.
The solution is to not use a blocking while loop but implement an event system driven loop. Then you process one line on every event loop cycle until you run out of work.
Move the file, stream and all that stuff outside of the function, make them class members so they persist. Then in run() you simply setup the input for reading, and move all the event posting stuff to a new function, for example doStep(), and in run() after you setup you have a:
QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
In doStep(), aside from the event stuff, at the end you also do scheduling:
if (!in.atEnd()) QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
else // we are done, close file, cleanup, whatever
This way only one step will be executed per event loop cycle, allowing the event loop to spin and process the events. While there is work, a step will be scheduled for the next event loop cycle. You can also use a single shot timer to do this, or even a queued connection, which you can trigger by emitting overloading the event to emit a signal on completed. You do not, and should not force events to be processed, using this approach there will be no need to.
The concept in general is about a non-blocking async event driven self scheduling worker, I have posted a fully implemented one here. The upsides are you can track progress, pause, cancel and all that good stuff.
Try sending event to QGraphicsView's viewport:
qApp->sendEvent(view->viewport(), &mousePressEvent);

How to transfer Control back to the previous form?

I am new to Qt.
I am doing a project using Qt Creator. In my project, I have one mainWindow. From the main window I start 4 screens (one after another, showing Initialization process). A new QDialog screen is opened, if there are any errors on any screen. My error screens have two Button (Retry, Continue). If i press Retry, i have to restart the initialization process over again.
eg;
void ErrorScreen1::on_Retry_pressed()
{
Screen1 *scrn = new Screen1(this);
scrn->show();
this->close();
}
In above example, it restarts the process.
Is there any way, I can start the initialization process from the point it was left?
Thanks in advance,
In common you should somehow keep the current state of your process and then you can restore it.
Have you considered making those four dialogs into a wizard with four steps? The next button could work for the continue, and you could have a retry button on each page that it makes sense, with just that page doing the retry logic necessary.
I resolved this issue by using QMessageBox as my error window.
It allowed me to start my process from the point i left.
void Screen1::ErrorMessage()
{
timer->stop();
QMessageBox *msgbox = new QMessageBox(this);
msgbox->setWindowTitle("ERROR MESSAGE");
msgbox->setText("Initialization Failed.");
msgbox->setStandardButtons(QMessageBox::Cancel | QMessageBox::Retry);
msgbox->setDefaultButton(QMessageBox::Retry);
int ret = msgbox->exec();
switch (ret)
{
case QMessageBox::Retry: timer->start(); break;
case QMessageBox::Cancel:
timer->disconnect();
ui->progressBar->setValue(0);
break;
default: break;
}
}

QDialog::accept quits Main Application

I have a ClientSocket Class which is a TcpSocket in a certain state of conversation I need to ask the user to enter a Communication password. So I've created a Dialog DG::ChallangeDialog . in DG::ChallangeDialogs ctor I've
ui->setupUi(this);
QPushButton* okButton = ui->buttonBox->button(QDialogButtonBox::Ok);
if(okButton != 0x0){
okButton->setText("Challange");
}
QObject::connect(this, SIGNAL(accepted()), this, SLOT(acceptedSlot()));
acceptedSlot again emits a signal challanged(QString)
void ChallangeDialog::acceptedSlot(){
QString text = ui->passBox->text();
emit challanged(text);
}
in ClientSocket I do
case Hallo:{
if(m->message().startsWith("welcome")){
DG::ChallangeDialog* dlg = new DG::ChallangeDialog;
dlg->setModal(true);
connect(dlg, SIGNAL(challanged(QString)), this, SLOT(challanged(QString)));
dlg->exec();
/*
DG::MessagePacket* res = new DG::MessagePacket((int)Hallo);
res->setMessage("challange");
send(res);
state = Challange;
*/
}
}break;
In ClientSocket::challange slot I send a Message challange (text) over the socket and store the password.
and I expect the Dialog to hide there and the normal socket conversation to continue. and after the Dialog is accepted or rejected the main application quits (It quits it doesn't crash). Why ?
My Application has no Other Widgets. I just works like an QCoreApplication. But still I've used QApplication cause I've some GUI Needs.
Is this the only window that is shown at this time? If so, I would guess that your QApplication instance is set to quit when the last window is closed. It is true by default.
If this is the case, you should explicitly set this to false before showing any windows.

Resources