Qt Application: Simulating modal behaviour (enable/disable user input) - c++

I am currently working on an application that launches separate processes which display additional dialogs. The feature I am trying to implement is simulating modal behavior of these dialogs. More specifically, I need the application to stop processing all input, both mouse and keyboard, when the dialog is launched, and resume when it's closed.
It is not so important for the dialog to remain on top of the application, although if you can suggest how to do that without resorting to Always-On-Top behavior, that would be nice as well.
To note, the application is compiled under both Windows and Linux. Also, it is not an option to launch the dialogs directly. They are in separate executables. Also the application is a pretty complex piece of software, so disabling widgets individually is not an option, or at least a not very viable one.
I found lock() and unlock() functions in QApplication class in Qt 3.3. We are currently using Qt 4.5, which doesn't seem to have that API. As a matter of fact, Qt 4.5 QApplication class doesn't seem to provide access to the Event Loop either.
To summarize: How do I disable/enable user input in a Qt Application, both mouse and keyboard shortcuts?

gj already proposed this solution but I thought I'd paste my implementation just for reference:
Implement a filter class that will absorb user input actions.
class BusyAppFilter : public QObject
{
protected:
bool eventFilter( QObject *obj, QEvent *event );
};
bool BusyAppFilter::eventFilter(QObject *obj, QEvent *event)
{
switch ( event->type() )
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::HoverEnter:
case QEvent::HoverLeave:
case QEvent::HoverMove:
case QEvent::DragEnter:
case QEvent::DragLeave:
case QEvent::DragMove:
case QEvent::Drop:
return true;
default:
return QObject::eventFilter( obj, event );
}
}
Then place this code your QApplication class:
QCursor busyCursor( Qt::WaitCursor );
setOverrideCursor( busyCursor );
BusyAppFilter filter;
installEventFilter( &filter ) ;
//... do the process stuff ...
removeEventFilter( &filter );
restoreOverrideCursor();

To get full access to the application wide events, use QObject::installEventFilter() or QCoreApplication::setEventFilter() on your application object.
If your filter function returns true, Qt stops further processing of the event.
To not get too platform specific with the forwarding of the events to your other applications, i'd go for a suitable IPC mechanism.

As an alternative answer, you can create your own event loop and start running it if necessary. You would need to create a QEventLoop object, connect a signal from another process to its quit() slot (such as from a QProcess that you are running your other program in), then exec() the loop. If I read things correctly, nothing will be handled by your main event loop while that loop is running.

Related

Qt or any other lang. Event Loop

I'm a c++ programmer on Qt platform.
I'm wondering, how does the event loop "knows" to which widget to send an event, mainly mouse/keyboard events?
Is it done based on mouse coordinates and z-order?
What about events from keyboard?
Thanks
The event loop doesn't know. This is done in other bits of code.
The term you're looking for with the keyboard is "focus". Exactly one window has focus, systemwide (or at least one window per keyboard on multi-user systems). The OS delivers the keystrokes to that window. Qt just finds the Qt object from the native window handle. Similarly, mouse clicks are mostly handled by the OS.
It doesn't know.
When you want to capture an event, you must create an event filter that either captures the event, or allows it to be passed down.
Here's a very simple event filter that I created some time ago:
bool OGL_widget::eventFilter(QObject *obj, QEvent *event) {
switch (event->type()) {
case QEvent::KeyRelease:
case QEvent::KeyPress: {
QKeyEvent *key = static_cast<QKeyEvent*> (event);
if (!key->isAutoRepeat())
key_event_queue << *key;
}
break;
case 1001:
case 1002: {
Savestate_event *save = static_cast<Savestate_event*> (event);
save_event_queue << *save;
}
break;
}
return QObject::eventFilter(obj, event);
}
Take a look at this well written article of events at Qt docs.

QTest asynchronous signal interception

I am investigating QTest for GUI testing. It appears that there is no mechanism in QTest to asynchronously test a signal callback. Am I misunderstanding how to use QTest, or misunderstanding the intended functionality provided by QTest?
For example, I am attempting to test a signal which launches a modal QMessageBox popup in response to clicking a QPushButton. I want to test events and state between the clicking of the button and clicking 'OK' on the QMessageBox. I have tried using QSignalSpy.wait(), QTestEventList.simulate(), QTest::mouseClick(), QTest::mouseEvent(), and QTRY_VERIFY(), all of which, when called from the testing code, do not return until after clicking 'OK' on the QMessageBox popup. Therefore, to test anything before all event handlers return, I would have to do something like install an event filter on the object under test, and write my own asynchronous testing code.
This is all the same code we would have to write if we weren't using a test library and we can do all of this without using QTest. For example, we can manually get a handle to the object under test, connect signals for testing, invoke an event to trigger the event handler or invoke the signal manually, and also write the installed test handlers that interact with the test environment before returning execution to the point at which the event was launched. What does QTest gain us here?
(Also posted on Qt forums)
Working with synchronous events using qtestlib is a bit tricky. If you look into the sources of qtestlib, you can find that event simulation are pretty straightforward. So, qtestlib doesn't provide any methods to handle synchronous events. Anyway, it's possible to handle Qt modal windows which are spawned by your app.
Main note to this question is that GUI objects can't be accessed from others threads except GUI thread. And moreover GUI could be created only in thread where QApplication was created. So some tricks like spawning a new thread to press OK button in QMessageBox will be unsuccessful with error like this object can not be accessed from other thread somewhere in QWidget sources.
To avoid this case async event could be triggered with Qt slots mechanism. First of all you should define a Helper object with some slot, for example
class Helper {
Helper() {}
public slots:
doSmth();
}
Further you should create an instance of this object in the testcase
void BlahblahTest::testCase1() {
Helper h;
...
And before you invoke some synchronous event with, for example, QTest::mouseClick, set a delayed action with
QTimer::singleShot(delay, &h, SLOT(doSmth));
Depends on your needs the implementation of doSmth could be like that
void Helper::doSmth() {
QList<QWidget *> tlw = qApp()->topLevelWidgets();
foreach (QWidget *w, tlw) {
if (...) { // w is a messagebox
Qt::keyClick(w, Qt::Key_Enter);
}
}
}

How to keep the application running when the last window is closed?

I'm working on a very basic GUI project in Qt (using c++) and want to be able to close the main window in my program without the program quitting all the way. By default, it will exit when the main window is closed. How to prevent that?
Set the QApplication::quitOnLastWindowClosed property to false:
qApp->setQuitOnLastWindowClosed(false);
If you still need your window to exist, you would probably like to reimplement your closeEvent method like this:
void MainWindow::closeEvent(QCloseEvent *event)
{
hide();
event->ignore();
}
or use QGuiApplication::setQuitOnLastWindowClosed(false)
If you want to perform some pre-exit operation as saving settings, connect some slot doing what you want to QCoreApplication::aboutToQuit()

Is there a way to determine if a top level Qt window has been moved?

I am trying to determine when the main window of my application has been moved. The main window is a standard QMainWindow and we've installed an eventFilter on the QApplication to look for moveEvents for the QMainWindow, but none are being triggered. For a variety of reasons, subclassing the QMainWindow isn't really an option.
Any thoughts on this, aside from starting a QTimer tto constantly check the position, would greatly be appreciated.
I guess it's better to install the event filter at the top-level window, instead of the application. However, if you still do not get QMoveEvents and you're working on Windows, you probably can override winEventFilter() and wait for WM_MOVE. Similar functionality might be available for Linux and Mac.
I usually do not recommend to break the platform-independence, but sometimes it might make sense.
Subclassing is really the best solution :-/
In the class that implements your top level windows you just overload this function:
virtual void moveEvent ( QMoveEvent * event )
From the documentation:
This event handler can be
reimplemented in a subclass to receive
widget move events which are passed in
the event parameter. When the widget
receives this event, it is already at
the new position.
The old position is accessible through
QMoveEvent::oldPos().
This should allow you to detect if your main window has moved. Why can't you subclass? Are you using an instance of QMainWindow directly? The usual use case is to subclass it anyway.

How do I detect application Level Focus-In in Qt 4.4.1?

I need to determine when my Qt 4.4.1 application receives focus.
I have come up with 2 possible solutions, but they both don’t work exactly as I would like.
In the first possible solution, I connect the focusChanged() signal from qApp to a SLOT. In the slot I check the ‘old’ pointer. If it ‘0’, then I know we’ve switched to this application, and I do what I want. This seems to be the most reliable method of getting the application to detect focus in of the two solutions presented here, but suffers from the problem described below.
In the second possible solution, I overrode the ‘focusInEvent()’ routine, and do what I want if the reason is ‘ActiveWindowFocusReason’.
In both of these solutions, the code is executed at times when I don’t want it to be.
For example, I have this code that overrides the focusInEvent() routine:
void
ApplicationWindow::focusInEvent( QFocusEvent* p_event )
{
Qt::FocusReason reason = p_event->reason();
if( reason == Qt::ActiveWindowFocusReason &&
hasNewUpstreamData() )
{
switch( QMessageBox::warning( this, "New Upstream Data Found!",
"New upstream data exists!\n"
"Do you want to refresh this simulation?",
"&Yes", "&No", 0, 0, 1 ) )
{
case 0: // Yes
refreshSimulation();
break;
case 1: // No
break;
}
}
}
When this gets executed, the QMessageBox dialog appears. However, when the dialog is dismissed by pressing either ‘yes’ or ‘no’, this function immediately gets called again because I suppose the focus changed back to the application window at that point with the ActiveWindowFocusReason. Obviously I don’t want this to happen.
Likewise, if the user is using the application opening & closing dialogs and windows etc, I don’t want this routine to activate. NOTE: I’m not sure of the circumstances when this routine is activated though since I’ve tried a bit, and it doesn’t happen for all windows & dialogs, though it does happen at least for the one shown in the sample code.
I only want it to activate if the application is focussed on from outside of this application, not when the main window is focussed in from other dialog windows.
Is this possible? How can this be done?
Thanks for any information, since this is very important for our application to do.
Raymond.
I think you need to track the QEvent::ApplicationActivate event.
You can put an event filter on your QApplication instance and then look for it.
bool
ApplicationWindow::eventFilter( QObject * watched, QEvent * event )
{
if ( watched != qApp )
goto finished;
if ( event->type() != QEvent::ApplicationActivate )
goto finished;
// Invariant: we are now looking at an application activate event for
// the application object
if ( !hasNewUpstreamData() )
goto finished;
QMessageBox::StandardButton response =
QMessageBox::warning( this, "New Upstream Data Found!",
"New upstream data exists!\n"
"Do you want to refresh this simulation?",
QMessageBox::Yes | QMessageBox::No) );
if ( response == QMessageBox::Yes )
refreshSimulation();
finished:
return <The-Superclass-here>::eventFilter( watched, event );
}
ApplicationWindow::ApplicationWindow(...)
{
if (qApp)
qApp->installEventFilter( this );
...
}
When your dialog is open, keyboard events don't go to your main window. After the dialog is closed, they do. That's a focus change. If you want to ignore the case where the focus switched from another window in your application, then you need to know when any window in your application has the focus. Make a variable and add a little more logic to your function. This will take some care, as the dialog will lose focus just before the main window gains focus.
Looking at the Qt docs it seems that focus events are created each time a widget gets the focus, so the sample code you posted won't work for the reasons you stated.
I am guessing that QApplication::focusedChanged does not work the way you want because some widgets don't accept keyboard events so also return null as the "old" widget even when changing focus within the same app.
I am wondering whether you can do anything with QApplication::activeWindow()
Returns the application top-level window that has the keyboard input focus, or 0 if no application window has the focus. Note that there might be an activeWindow() even if there is no focusWidget(), for example if no widget in that window accepts key events.