Implementing Timeout in QT - qt

I am trying to implement timeout in QT. I want to perform following task so I require timeout.
In application i have implemented menu. If I entered choose of option from menu, it will execute related screen. This screen should timeout after 15 seconds, if I am not getting any key event before 15 seconds. Following is my code:
bool cMeasurementUnit::eventFilter(QObject *obj, QEvent *event)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(event->type() == QEvent::KeyPress)
{
if((keyEvent ->key()) == Qt::Key_Tab)
{
if(m_pWidgetFirstTabFocus->hasFocus())
{
m_pWidgetFirstTabFocus->setStyleSheet(QString::fromUtf8("background-color: rgb(255, 255, 255);"));
}
m_pWidgetFirstTabFocus = m_pWidgetFirstTabFocus->nextInFocusChain() ;
while((m_pWidgetFirstTabFocus->focusPolicy()) == Qt::NoFocus)
{
m_pWidgetFirstTabFocus = m_pWidgetFirstTabFocus->nextInFocusChain() ;
if(m_pWidgetFirstTabFocus == this)
{
m_pWidgetFirstTabFocus = MEASUREMENT_UNIT_FIRST_TAB;
}
}
m_pWidgetFirstTabFocus->setStyleSheet(QString::fromUtf8("background-color: rgb(207, 207, 207);"));
}
else if((keyEvent ->key()) == Qt::Key_Return)
{
SaveChannelUnit();
return true ;
}
else if((keyEvent ->key()) == Qt::Key_Up)
{
if (((QComboBox *)m_pWidgetFirstTabFocus)->currentIndex() == 0)
{
((QComboBox *)m_pWidgetFirstTabFocus)->setCurrentIndex((((QComboBox *)m_pWidgetFirstTabFocus)->count() - 1)) ;
return true ;
}
}
else if((keyEvent ->key()) == Qt::Key_Left)
{
return true;
}
}
return QObject::eventFilter(obj, event);
}
I have tried to implement using QTimer::singleShot(15000, this, SLOT(DeleteClass()));
but it is not working. Please help me regarding this issue.I have implemented QTimer::singleShot inside if(event->type() == QEvent::KeyPress) statement in above code so that whenever I press a key it will reinitialize QTimer::singleShot and screen of class cMeasurementUnit will not get timeout, otherwise it will be timeout after 15seconds. Following is the code for DeleteClass, is it correct? if not will you please tell me correct way to do it? Thanks in advance
void cMeasurementUnit::DeleteClass()
{
DPRINTF("IN FUNCTION %s\n",__FUNCTION__);
delete this;
}

You can use QTimer to run checks periodically and QElapsedTimer to calculate inactivity time.
In the header:
QElapsedTimer elapsed_timer;
In the initialization:
elapsed_timer.start();
QTimer* timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(timeout()));
timer->start(1000); // number of milliseconds between checks
// install event filter for target widget, etc.
In the timeout slot:
if (elapsed_timer.elapsed() > 15000) { // timeout interval in msec
//perform close actions, e.g. widget->close()
}
In the event filter: the following code should be executed if an appropriate key event was received:
elapsed_timer.restart();

Related

QMainWindow not receiving keyPressEvent, even with event filter

I'm trying to design an image viewer application where I use a QGraphicsView to display my images. I want to enable the user to use the arrow keys to open the next/previous image, but my QGraphicsView is always consuming my keyPressEvent. I would like these events to be handled by my QMainWindow class instead. I realize this is a common issue, and apparently I can solve it by installing an event filter and/or ensuring that my QMainWindow can have focus. I have done both, but so far the only thing I have done is to not let the QGraphicsView get the event, but it still does not propagate to the QMainWindow. So far I've implemented the eventFilter method in my QMainWindow class and installed it on my QGraphicsView object.
QMainWindow class
IVMainWindow::IVMainWindow(QWidget *parent)
: QMainWindow(parent)
{
setWindowTitle("ImageViewer++");
setFocusPolicy(Qt::StrongFocus); // Enabled the QMainWindow to get focus
m_image_widget = new IVImageWidget();
m_image_widget->installEventFilter(this); // Install event filter on QGraphicsView
setCentralWidget(m_image_widget);
resize(QGuiApplication::primaryScreen()->availableSize() * 3 / 5);
// For DEBUG purpose
loadImage("res/image.png");
createMenuBar();
createToolBar();
}
/**
* Filters out arrow key presses.
*/
bool IVMainWindow::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
auto *keyEvent = static_cast<QKeyEvent *>(event);
int key = keyEvent->key();
// Return true to reject the key-presses
return (key == Qt::Key_Left || key == Qt::Key_Right || key == Qt::Key_Up || key == Qt::Key_Down);
} else {
// standard event processing
return QMainWindow::eventFilter(obj, event);
}
}
//Never gets to this point, unless I explicitly give it focus by clicking on some other widget than the QGraphicsView...
void IVMainWindow::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::RightArrow) {
m_logger.Debug("Right arrow pressed.");
} else if (event->key() == Qt::LeftArrow) {
m_logger.Debug("Left arrow pressed.");
}
}
You need to process the event in the event filter, it is ok to call QMainWindow::eventFilter for sanity but it do not
process the event as an incoming event, so the event handlers will not be called.
bool IVMainWindow::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
auto *keyEvent = static_cast<QKeyEvent *>(event);
int key = keyEvent->key();
// Return true to reject the key-presses
if (key == Qt::Key_Left || key == Qt::Key_Right || key == Qt::Key_Up || key == Qt::Key_Down)
{
//process event here somehow, or instruct your class to do it later
return true; //filter the event
}
} else {
// standard event processing
return QMainWindow::eventFilter(obj, event);
}
}
//This will only be called for the 'real' events, the ones that eventually are received by the main window
void IVMainWindow::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::RightArrow) {
m_logger.Debug("Right arrow pressed.");
} else if (event->key() == Qt::LeftArrow) {
m_logger.Debug("Left arrow pressed.");
}
}

How can I intercept and cancel the minimizing of a Window?

I have a Window subclass in my project, and at runtime the instance is created and shown entirely on the QML side. I know that I can prevent the window from being minimized by not including the WindowMinimizeButtonHint in the flags:, but I actually need to have the minimize button present and enabled but be able to intercept the minimize button click, cancel the actual minimizing, and do something else (FYI my client is requiring this non-standard windowing behavior, not me).
So far, the only thing I've been able to achieve is to handle the onWindowStateChanged: event, check if windowState === Qt.WindowStateMinimized and call show() from a timer (calling it inside the event handler directly does nothing). This results in the window moving down to the system tray and then suddenly coming back up to normal.
Is there any way to do this, something like an OnMinimized event that can be cancelled?
Edit: based on Benjamin T's answer, I'm at least part way to a solution for OSX:
#import <AppKit/AppKit.h>
bool NativeFilter::nativeEventFilter(const QByteArray &eventType,
void *message, long *result)
{
if (eventType == "mac_generic_NSEvent") {
NSEvent *event = static_cast<NSEvent *>(message);
if ([event type] == NSKeyDown) {
return true;
}
}
return false;
}
In this example I'm able to intercept and cancel all NSKeyDown events (while leaving other events like mouse clicks etc. still working). The remaining problem is that I still don't know to intercept a minimize event - NSEvent.h doesn't seem to have anything that covers that. Perhaps I need to cast to a different type of event?
Edit 2 - working solution:
I was not able to find any way to intercept the minimize event proper and cancel it, so my workaround is to instead intercept the click on the window, determine if the click is over the minimize button (or the close or zoom buttons) and cancel the event if so (and send a notification to my qml window that the click occurred). I also handle the case of double-clicking the titlebar to zoom the window, and using the Command-M keys to minimize the window.
First step is to implement a QAbstractNativeEventFilter. In your header:
#include <QAbstractNativeEventFilter>
class NativeFilter : public QAbstractNativeEventFilter {
public:
bool nativeEventFilter(const QByteArray &eventType, void *message,
long *result);
};
The implementation:
#import <AppKit/AppKit.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSButton.h>
bool NativeFilter::nativeEventFilter(const QByteArray &eventType, void
*message, long *result)
{
if (eventType == "mac_generic_NSEvent") {
NSEvent *event = static_cast<NSEvent *>(message);
NSWindow *win = [event window];
// TODO: determine whether or not this is a window whose
// events you want to intercept. I did this by checking
// [win title] but you may want to find and use the
// window's id instead.
// Detect a double-click on the titlebar. If the zoom button
// is enabled, send the full-screen message to the window
if ([event type] == NSLeftMouseUp) {
if ([event clickCount] > 1) {
NSPoint pt = [event locationInWindow];
CGRect rect = [win frame];
// event coordinates have y going in the opposite direction from frame coordinates, very annoying
CGFloat yInverted = rect.size.height - pt.y;
if (yInverted <= 20) {
// TODO: need the proper metrics for the height of the title bar
NSButton *btn = [win standardWindowButton:NSWindowZoomButton];
if (btn.enabled) {
// notify qml of zoom button click
}
return true;
}
}
}
if ([event type] == NSKeyDown) {
// detect command-M (for minimize app)
if ([event modifierFlags] & NSCommandKeyMask) {
// M key
if ([event keyCode] == 46) {
// notify qml of miniaturize button click
return true;
}
}
// TODO: we may be requested to handle keyboard actions for close and zoom buttons. e.g. ctrl-cmd-F is zoom, I think,
// and Command-H is hide.
}
if ([event type] == NSLeftMouseDown) {
NSPoint pt = [event locationInWindow];
CGRect rect = [win frame];
// event coordinates have y going in the opposite direction from frame coordinates, very annoying
CGFloat yInverted = rect.size.height - pt.y;
NSButton *btn = [win standardWindowButton:NSWindowMiniaturizeButton];
CGRect rectButton = [btn frame];
if ((yInverted >= rectButton.origin.y) && (yInverted <= (rectButton.origin.y + rectButton.size.height))) {
if ((pt.x >= rectButton.origin.x) && (pt.x <= (rectButton.origin.x + rectButton.size.width))) {
// notify .qml of miniaturize button click
return true;
}
}
btn = [win standardWindowButton:NSWindowZoomButton];
rectButton = [btn frame];
if (btn.enabled) {
if ((yInverted >= rectButton.origin.y) && (yInverted <= (rectButton.origin.y + rectButton.size.height))) {
if ((pt.x >= rectButton.origin.x) && (pt.x <= (rectButton.origin.x + rectButton.size.width))) {
// notify qml of zoom button click
return true;
}
}
}
btn = [win standardWindowButton:NSWindowCloseButton];
rectButton = [btn frame];
if ((yInverted >= rectButton.origin.y) && (yInverted <= (rectButton.origin.y + rectButton.size.height))) {
if ((pt.x >= rectButton.origin.x) && (pt.x <= (rectButton.origin.x + rectButton.size.width))) {
// notify qml of close button click
return true;
}
}
}
return false;
}
return false;
}
Then in main.cpp:
Application app(argc, argv);
app.installNativeEventFilter(new NativeFilter());
Generally speaking, you should use the event system ans not signal/slots to intercept events and changes.
The easiest way to do so is either to subclass the object you use and reimplement the appropriate event handler, or to use an event filter.
Since you are using QML, subclassing might be difficult as you don't have access to all Qt internal classes.
Here is what the code would look like when using event filtering.
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
auto root = engine.rootObjects().first();
root->installEventFilter(new EventFilter());
return app.exec();
}
class EventFilter : public QObject
{
Q_OBJECT
public:
explicit EventFilter(QObject *parent = nullptr);
bool eventFilter(QObject *watched, QEvent *event) override;
};
bool EventFilter::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::WindowStateChange) {
auto e = static_cast<QWindowStateChangeEvent *>(event);
auto window = static_cast<QWindow *>(watched);
if (window->windowStates().testFlag(Qt::WindowMinimized)
&& ! e->oldState().testFlag(Qt::WindowMinimized))
{
// Restore old state
window->setWindowStates(e->oldState());
return true;
}
}
// Do not filter event
return false;
}
However, you will quickly run into the same issue that when using the signal/slot mechanism: Qt only notify you when the window has already been minimized. Meaning that restoring the window at this point will make a hide/show effect.
So you need to go deeper and you a native event filter.
The following code works on Windows, you should adapt it for macOS:
class NativeFilter : public QAbstractNativeEventFilter {
public:
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);
};
bool NativeFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
/* On Windows we interceot the click in the title bar. */
/* If we wait for the minimize event, it is already too late. */
#ifdef Q_OS_WIN
auto msg = static_cast<MSG *>(message);
// Filter out the event when the minimize button is pressed.
if (msg->message == WM_NCLBUTTONDOWN && msg->wParam == HTREDUCE)
return true;
#endif
/* Example macOS code from Qt doc, adapt to your need */
#ifdef Q_OS_MACOS
if (eventType == "mac_generic_NSEvent") {
NSEvent *event = static_cast<NSEvent *>(message);
if ([event type] == NSKeyDown) {
// Handle key event
qDebug() << QString::fromNSString([event characters]);
}
}
#endif
return false;
}
In your main():
QGuiApplication app(argc, argv);
app.installNativeEventFilter(new NativeFilter());
For more info, you can read the Qt documentation about QAbstractNativeEventFilter.
You may need to use QWindow::winId() to check to which window the native events are targeted.
As I am not a macOS developer, I do not know what you can do with NSEvent.
Also it seems the NSWindowDelegate class could be useful to you: https://developer.apple.com/documentation/appkit/nswindowdelegate
If you can retrieve a NSWindow from QWindow::winId(), you should be able to use it.

QT QLineEdit focusout

I have a QLineedit with mask and qvalidator (subclassed)
How can i prevent to move away the focus if the input isn't match the mask or validator ?
Because neither mask nor qvalidator don't prevent to move away focus from QLineEdit.
And editingfinished isn't work because :
void QLineEdit::editingFinished()
This signal is emitted when the Return or Enter key is pressed or the line edit loses focus. Note that if there is a validator() or inputMask() set on the line edit and enter/return is pressed, the editingFinished() signal will only be emitted if the input follows the inputMask() and the validator() returns QValidator::Acceptable."
void MainWindow:n_lineEdit_editingFinished()
{
if (ui->lineEdit->text() != "1111") ui->lineEdit->setFocus();
}
So mask (an validator) doesn't work together with editingFinsihed signal.
plus i have tried this
bool MainWindow::eventFilter(QObject *filterObj, QEvent *event)
{
if (filterObj == ui->lineEdit ) {
if(event->type() == QEvent::FocusOut) {
if (ui->lineEdit->text() != "1111") { ui->lineEdit-`>setFocus();};
return true;
};
};
return false;
}
thank you Attila
From the Qt's doc:
Note that if there is a validator set on the line edit, the
returnPressed()/editingFinished() signals will only be emitted if the
validator returns QValidator::Acceptable.
But you can set a focus on every event, not only for FocusOut:
bool MainWindow::eventFilter(QObject *filterObj, QEvent *event)
{
if (filterObj == ui->lineEdit )
ui->lineEdit->setFocus();
if(event->type() == QEvent::KeyRelease)
{
QKeyEvent* e = (QKeyEvent*)event;
if(e->key() == Qt::Key_Return
|| e->key() == Qt::Key_Enter)
{
/* do what you want here */
}
}
return QObject::eventFilter(filterObj, event); // usual process other events
}

Long Mouse Press on QToolButton calls the released signal

I have a QToolButton defined. I have written two slots for pressed and released signals.
void EyImgGalleryWindow::toolPressed()
{
if(Uptimer->timerId() == -1)
Uptimer->start();
m_CustomGalleryView->scrollUp();
}
void EyImgGalleryWindow::toolReleased()
{
Uptimer->stop();
m_CustomGalleryView->scrollUpRelease();
}
When the pressed() signal is emitted, I am starting a timer and when the released signal is emitted I am stopping the timer.
So here my implementation is when the timer exceeds 3s I am updating a variable. I have to update the variable only when the User have a long press on this QToolButton.
But here my implementation is failing in this case. When I have a long press on QToolButton, It is emitting the Pressed and Released signal on equal intervals. When a long press is there we should get only once the Released signal and Why here it is calling the Released signal multiple times. Is there anything i am doing wrong here?
You can catch long press with next event filter and QElapsedTimer:
QElapsedTimer el;//somewhere
//...
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if(obj == ui->pushButton_13 && event->type() == QEvent::MouseButtonPress)
{
el.start();
}
if(obj == ui->pushButton_13 && event->type() == QEvent::MouseButtonRelease)
{
qDebug() << el.elapsed();//in milliseconds, you can divide by 1000 to get seconds
if(el.elapsed() > 1000)
qDebug() << "long";
else
qDebug() << "not long";
}
return QObject::eventFilter(obj, event);
}
It will work for pushButtons, labels, toolButtons etc.

File changes handle, QSocketNotifier disables due to invalid socket

I am developing for a touch screen and need to detect touch events to turn the screen back on. I am using Qt and sockets and have run into an interesting issue.
Whenever my QSocketNotifier detects the event it sends me infinite notices about it. Therefore I need to close and open the event file to cycle the notifier (inputNotifier in the below code). The problem usually arises several minutes after the device has been running and the file (inputDevice) suddenly changes it's handle from 24 to something else (usually 17).
I am not sure what to do, because the initial connect statement is linked to the initial Notifier pointer. If I create a new Notifier using the new handle, the connect is invalid. As far as I can tell there is no option to set a new socket value on a running QSocketNotifier. Suggestions? The relevant code is below:
#include "backlightcontroller.h"
#include <QTimer>
#include <QFile>
#include <syslog.h>
#include <QDebug>
#include <QSocketNotifier>
BacklightController::BacklightController(QObject *parent) :
QObject(parent)
{
backlightActive = true;
// setup timer
trigger = new QTimer;
trigger->setSingleShot(false);
connect(trigger, SIGNAL(timeout()), SLOT(deactivateBacklight()));
idleTimer = new QTimer;
idleTimer->setInterval(IDLE_TIME * 1000);
idleTimer->setSingleShot(false);
connect(idleTimer, SIGNAL(timeout()), SIGNAL(idled()));
idleTimer->start();
// setup socket notifier
inputDevice = new QFile(USERINPUT_DEVICE);
if (!inputDevice->open(QIODevice::ReadOnly))
{
syslog (LOG_ERR, "Input file for Backlight controller could not been opened.");
}
else
{
inputNotifier = new QSocketNotifier(inputDevice->handle(), QSocketNotifier::Read);
inputNotifier->setEnabled(true);
connect(inputNotifier, SIGNAL(activated(int)), SLOT(activateBacklight()));
}
qDebug()<<"backlight socket: "<<inputNotifier->socket();
// read out settings-file
QString intensity = Global::system_settings->getValue("BelatronUS_backlight_intensity");
if (intensity.length() == 0) intensity = "100";
QString duration = Global::system_settings->getValue("BelatronUS_backlight_duration");
if (duration.length() == 0) duration = "180";
QString always_on = Global::system_settings->getValue("BelatronUS_backlight_always_on");
if (always_on.length() == 0) always_on = "0";
setIntensity(intensity.toInt());
setDuration(duration.toInt());
if (always_on == "0")
setAlwaysOn(false);
else
setAlwaysOn(true);
}
BacklightController::~BacklightController()
{
trigger->stop();
inputNotifier->setEnabled(false);
inputDevice->close();
delete trigger;
delete inputDevice;
delete inputNotifier;
}
void BacklightController::setCurrentIntensity(int intensity)
{
// adapt backlight intensity
QFile backlightFile("/sys/class/backlight/atmel-pwm-bl/brightness");
if (!backlightFile.open(QIODevice::WriteOnly))
{
syslog (LOG_ERR, "Backlight intensity file could not been opened.");
}
else
{
QString intensityString = QString::number(TO_BRIGHTNESS(intensity));
if (backlightFile.write(
qPrintable(intensityString), intensityString.length()
) < intensityString.length())
{
syslog (LOG_ERR, "Backlight intensity could not been changed.");
}
backlightFile.close();
}
}
void BacklightController::resetNotifier()
{
inputDevice->close();
if (!inputDevice->open(QIODevice::ReadOnly))
{
syslog (LOG_ERR, "BacklightController::%s: Input file could not been opened.", __FUNCTION__);
}
qDebug()<<"reset, handle: "<<inputDevice->handle();
//inputNotifier=QSocketNotifier(inputDevice->handle(), QSocketNotifier::Read);
// restart timer after user input
idleTimer->start();
}
void BacklightController::activateBacklight()
{
// only activate backlight, if it's off (avoid to useless fileaccess)
if (!backlightActive)
{
setCurrentIntensity(_intensity);
backlightActive = true;
emit backlightActivated();
}
// restart backlight timeout, but only if we don't want the backlight to shine all the time
if (!_alwaysOn)
trigger->start();
// reset notifier to be able to catch the next userinput
resetNotifier();
}
void BacklightController::deactivateBacklight()
{
// don't turn it off, if it's forced on
if (!_alwaysOn)
{
if (backlightActive)
{
// only deactivate backlight, if it's on (avoid to useless fileaccess)
setCurrentIntensity(BACKLIGHT_INTENSITY_OFF);
backlightActive = false;
emit backlightDeactivated();
}
}
qDebug()<<"trigger stopping";
trigger->stop();
}
void BacklightController::setIntensity(int intensity)
{
if (intensity > 100)
intensity = 100;
else if (intensity < 0)
intensity = 0;
_intensity = intensity;
// write new intensity to file if it's active at the moment
if (backlightActive)
{
setCurrentIntensity(_intensity);
trigger->start();
}
}
void BacklightController::setDuration(int duration)
{
if (duration < 1)
duration = 1;
_duration = duration;
trigger->setInterval(_duration * MS_IN_SEC);
// reset trigger after changing duration
if (backlightActive)
{
trigger->start();
}
}
void BacklightController::setAlwaysOn(bool always_on)
{
_alwaysOn = always_on;
// tell the timer what to to now
if (_alwaysOn)
{
this->activateBacklight();
trigger->stop();
}
else
{
trigger->start();
}
}
I seem to have found a working solution for now. It's not the greatest so if there are better solutions I would be interested to hear them. The reason I did not think of this before is because I thought if I had a new connect statement in a function it would have limited scope as the function ended.
The solution was to simply check for an occurrence of the handle change in the file and then create a new pointer for the notifier using that handle. Then re-enable the notifier because it has likely been disabled by now and then create a new connect statement for the pointer. This is the code I used, added just below the closing and reopening of the event file:
if(inputDevice->handle()!=inputNotifier->socket()){
inputNotifier = new QSocketNotifier(inputDevice->handle(), QSocketNotifier::Read);
inputNotifier->setEnabled(true);
connect(inputNotifier, SIGNAL(activated(int)), SLOT(activateBacklight()));
}

Resources