I have a Qt app with several window shortcuts defined in a Qt Designer form action. The shortcuts works well until they are pressed while the focus is on a widget that handles the same combination (overriding my window shortcut).
I would like to have the opposite behavior: window shortcuts overriding focused widget shortcuts.
I tried using eventFilter and I can catch the desired events, but I'm not able to resend them in a way that the global shortcuts are called. I could use a big switch and call the actions myself, but of course, I would like to avoid that.
I used postEvent and sendEvent inside the eventFilter using the MainWindow as the receiver, but the events are ignored:
bool MainWindow::eventFilter(QObject*, QEvent* event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Z
&& keyEvent->modifiers() == Qt::ControlModifier) {
//Calling the triggers directly works
ui->actionUndo->trigger();
return true;
} else if (keyEvent->modifiers().testFlag(
Qt::KeypadModifier)) {
QKeyEvent* ev2
= new QKeyEvent(keyEvent->type(), keyEvent->key(), 0);
qDebug() << keyEvent << ev2;
//This sendEvent doesn't work
QApplication::sendEvent(ui->widget, ev2);
event->accept();
return true;
} else {
return false;
}
}
return false;
}
As one of the solutions you can install QEvent::ShortcutOverride event filters:
For QEvent::ShortcutOverride the receiver needs to explicitly accept
the event to trigger the override. Calling ignore() on a key event
will propagate it to the parent widget. The event is propagated up the
parent widget chain until a widget accepts it or an event filter
consumes it.
That event would be called when some widget tries to override a shortcut event, e.g. just a simple example:
I have just a new Qt app with one lineEdit and window menu with Ctrl+V shortcut (overrides the paste shortcut in lineEdit).
Here how it works:
1.Create the filtering method that would ignore (return true) the shortcut overrides (I've used MainWindow::eventFilter in sample app, however you can use any filtering object you need or want). It might be better to follow Qt docs and use the accept()/ignore() as stated above, however on my app it worked just fine without them just returning true/false.
2.Install the event filter from p.1 to widget that should ignore shortcut action if overriding.
3.I've added the menu action with Ctrl+V shortcut in designer. In code below you would see the "Hello from window shortcut!" - the menu action result, when trying to Paste (Ctrl+V) instead of actual lineEdit paste operation.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->lineEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::ShortcutOverride) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
// Ignore only the Ctrl + V shortcut override, you can customize check for your needs
if (keyEvent->modifiers().testFlag(Qt::ControlModifier) && keyEvent->key() == 'V') {
qDebug() << "Ignoring" << keyEvent->modifiers() << "+" << (char)keyEvent->key() << "for" << watched;
event->ignore();
return true;
}
}
return QMainWindow::eventFilter(watched, event);
}
void MainWindow::on_action1_triggered()
{
qDebug() << "Hello from window shortcut!";
}
Sample debug output:
Ignoring QFlags(ControlModifier) + V for QLineEdit(0x575b10, name = "lineEdit")
Hello from window shortcut!
Note: Unfortunatelly you should install such filters for all widgets you want not to override the shortcuts manually.
UPDATE: Shortly - you are ignoring the underlying widget shortcut event and propagating it to the parent widget.
Below is comparison for Ctrl-Z (triggers Undo in edit) and Ctrl-V (ignored in edit instead of Paste, and triggers menu Action):
Block I - events in beginning are the same for both Ctrl-Z and ignored Ctrl-V:
QLineEdit recieves QKeyEvent(ShortcutOverride, Key_Control, ControlModifier)
QLineEdit recieves QKeyEvent(KeyPress, Key_Control, ControlModifier)
MainWindow recieves QKeyEvent(KeyPress, Key_Control, ControlModifier)
QLineEdit recieves QKeyEvent(ShortcutOverride, Key_Z, ControlModifier)
Block II - where the difference happens...
For Ctrl-Z - lineEdit receives the Ctrl+Z KeyPress event, triggering the Undo operation:
QLineEdit recieves QKeyEvent(KeyPress, Key_Z, ControlModifier)
Here the MainWindow recieves no events, not depending of has it Ctrl+Z action shortcuts or not, it's just swallowed by QLineEdit
For Ctrl-V - MainWindow receives the Ctrl+V ShortcutOverride event propagated from QLineEdit:
Ignoring "Ctrl+V" for QLineEdit code executes in filterEvent
MainWindow recieves QKeyEvent(ShortcutOverride, Key_V, ControlModifier)
"Hello from window shortcut!" code from menu Action triggered slot executed.
Here the QLineEdit receives no events after filter tells it to ignore ShortcutOverride, the MainWindow shortcut executed instead
Block III - events in ending are also the same for both Ctrl-Z and ignored Ctrl-V - just key release events:
QLineEdit recieves QKeyEvent(KeyRelease, Key_Z, ControlModifier)
MainWindow recieves QKeyEvent(KeyRelease, Key_Z, ControlModifier)
QLineEdit recieves QKeyEvent(KeyRelease, Key_Control)
MainWindow recieves QKeyEvent(KeyRelease, Key_Control)
P.S. I really don't know why it happens the exactly so - but this is just how it works :)
Related
I am creating an app using QtCreator 4.12 and am using a touch screen for input. I want a virtual keyboard to pop up when any QLineEdit is in focus and the keyboard to disappear when the QLineEdit is out of focus. I am using matchbox for the virtual keyboard and am running:
keyboard->start("/bin/sh",QStringList()<<"keyboard.sh");
where the keyboard.sh file runs the matchbox-keyboard command to get the keyboard on screen.
The keyboard does pop up on screen when QLineEdit is in focus. I am unable to make it go away when it's out of focus.
Haven't tested that, but my idea would be to derive from QLineEdit and emit signals like focusGotten and focusLost (names from the top of my head, feel free to use any you prefer) accordingly (say, from focus[In|Out]Event), so they can call kill or terminate on the subprocess (when connected to those slots).
There's no "focusLost" signal but if you want to know when your widget has lost focus, override and reimplement void QWidget::focusOutEvent(QFocusEvent* event) in your widget.
It will be called whenever your widget has lost focus, at that point you can check if your keyboard is still visible (or if the process is still running) and do wathever you want.
EDIT
Here is an example on how to reimplement focus events:
In your .h file
class YourQLineEditClass : public QLineEdit
{
Q_OBJECT
.
.
.
protected:
void focusInEvent(QFocusEvent* e);
void focusOutEvent(QFocusEvent* e);
QProcess* keyboardProcess;
};
In your .cpp file
void YourQLineEditClass::focusInEvent(QFocusEvent* e)
{
// Create the keyboard process when line has focus
keyboardProcess = new QProcess(this);
keyboardProcess->start("/bin/sh", QStringList() << "keyboard.sh");
}
void YourQLineEditClass::focusOutEvent(QFocusEvent* e)
{
// Kill the keyboard process when line has lost focus
if (keyboardProcess != nullptr && keyboardProcess->isRunning())
{
keyboardProcess->terminate(); // or kill()
keyboardProcess->deleteLater();
}
}
I have implemented a sort of gestures listener in the parent widget. If the mouse event is a tap ( press and release with out any move events) then the children of that widget should handle the event, if not, and the events describe a swipe, then the parent widget should handle the events. Is there any way to redirect events to the parents first, then rebroadcast them so that the appropriate child could handle it if the need arises.
I think http://qt-project.org/doc/qt-4.8/qobject.html#installEventFilter is what you are looking for. So what you do is that you set any class inheriting from QObject and set it as the event filter. So to quote from the DOCs:
An event filter is an object that receives all events that are sent to this object.
The filter can either stop the event or forward it to this object.
The example in the link is for a QKeyEvent but can obviously be adapted to use various mouse functions as well. Below is a dummy example that would 'eat' the first click on a QPushButton while if you double click, the event would go through as normal (note that in this particular example first you would call the 'eat' sequence and upon the second click in a short time the button would take over).
bool MyWidget::eventFilter(QObject *obj, QEvent *event)
{
if( obj != pushButton ) {
return QObject::eventFilter(obj, event);
}
if (event->type() == QEvent::MouseButtonPress ) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
qDebug("Intercepted mouse click with button %d", mouseEvent->button());
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
Does this help?
I created a simple QListView added a QStringListModel that allow items to be added and their text edited. But I don't want to allow empty fields to be added and I partially achieved this by using the dataChanged signal from the model which is emitted if the list item loses focus without text inserted or the user presses the Enter key without adding text.
However if you press the Esc key, the field remains empty and no dataChanged signal is emitted. How can I get notified if the field was left empty without overloading the QListView class which would be tedious(I used the designer to create the form)?
Is there another signal that is emitted or method I can use to achieve that?
Thanks!
Use event filters in your main GUI class:
void GUI::GUI()
{
ui->mListView->installEventFilter(this);
}
bool GUI::eventFilter(QObject *object, QEvent *event)
{
if (object == ui->mListView && event->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_Escape)
// special Esc handling here
}
else
return false;
}
It's simple to use your custom widgets in Qt Designer. Right click on the QListView and choose Promote to ... there add a new class and apply it to the widget.
http://qt-project.org/doc/qt-4.8/designer-using-custom-widgets.html
In my program, I'd like to have mouseMoveEvent(QMouseEvent* event) called whenever the mouse moves (even when it's over another window).
Right now, in my mainwindow.cpp file, I have:
void MainWindow::mouseMoveEvent(QMouseEvent* event) {
qDebug() << QString::number(event->pos().x());
qDebug() << QString::number(event->pos().y());
}
But this seems to only be called when I click and drag the mouse while over the window of the program itself. I've tried calling
setMouseTracking(true);
in MainWindow's constructor, but this doesn't seem to do anything differently (mouseMoveEvent still is only called when I hold a mouse button down, regardless of where it is). What's the easiest way to track the mouse position globally?
You can use an event filter on the application.
Define and implement bool MainWindow::eventFilter(QObject*, QEvent*). For example
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseMove)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
statusBar()->showMessage(QString("Mouse move (%1,%2)").arg(mouseEvent->pos().x()).arg(mouseEvent->pos().y()));
}
return false;
}
Install the event filter when the MainWindows is constructed (or somewhere else). For example
MainWindow::MainWindow(...)
{
...
qApp->installEventFilter(this);
...
}
I had the same problem, further exacerbated by the fact that I was trying to call this->update() to repaint the window on a mouse move and nothing would happen.
You can avoid having to create the event filter by calling setMouseTracking(true) as #Kyberias noted. However, this must be done on the viewport, not your main window itself. (Same goes for update).
So in your constructor you can add a line this->viewport()->setMouseTracking(true) and then override mouseMoveEvent rather than creating this filter and installing it.
In Qt, when a widget receives focus, how can get a notification about it, so I can execute some custom code? Is there a signal or an event for that?
You can add en event filter.
This is an example of an application written with QtCreator. This form has a QComboBox named combobox.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->comboBox->installEventFilter(this);
.
.
.
}
bool MainWindow::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::FocusOut)
{
if (object == ui->comboBox)
{
qWarning(object->objectName().toLatin1().data());
}
}
return false;
}
There is a "focusChanged" signal sent when the focus changes, introduced in Qt 4.1.
It has two arguments, he widget losing focus and the one gaining focus:
void QApplication::focusChanged(QWidget * old, QWidget * now)
Qt Designer isn't designed for this level of WYSIWYG programming.
Do it in C++:
class LineEdit : public QLineEdit
{
virtual void focusInEvent( QFocusEvent* )
{}
};
The simplest way is to connect a slot to the QApplication::focusChanged signal.
I'd have to play with it, but just looking at the QT Documentation, there is a "focusInEvent". This is an event handler.
Here's how you find information about.... Open up "QT Assistant". Go to the Index. Put in a "QLineEdit". There is a really useful link called "List of all members, including inherited members" on all the Widget pages. This list is great, because it even has the inherited stuff.
I did a quick search for "Focus" and found all the stuff related to focus for this Widget.
You have hit on of the weird splits in QT, if you look at the documentation focusInEvent is not a slot it is a protected function, you can override it if you are implementing a subclass of your widget. If you you just want to catch the event coming into your widget you can use QObject::installEventFilter it let's you catch any kind of events.
For some odd reason the developers of Trolltech decided to propagate UI events via two avenues, signals/slots and QEvent
Just in case anybody looking for two QMainWindow focus change .
You can use
if(e->type() == QEvent::WindowActivate)
{
//qDebug() << "Focus IN " << obj << e ;
}
QWidget::setFocus() is slot, not signal. You can check if QLineEdit is in focus with focus property. QLineEdit emits signals when text is changed or edited, see documentation.