Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt override widget shortcut (window shortcut)

Tags:

qt

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;
}
like image 241
dv1729 Avatar asked Jul 04 '17 22:07

dv1729


1 Answers

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:

  1. QLineEdit recieves QKeyEvent(ShortcutOverride, Key_Control, ControlModifier)
  2. QLineEdit recieves QKeyEvent(KeyPress, Key_Control, ControlModifier)
  3. MainWindow recieves QKeyEvent(KeyPress, Key_Control, ControlModifier)
  4. 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:

  1. 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:

  1. Ignoring "Ctrl+V" for QLineEdit code executes in filterEvent
  2. MainWindow recieves QKeyEvent(ShortcutOverride, Key_V, ControlModifier)
  3. "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:

  1. QLineEdit recieves QKeyEvent(KeyRelease, Key_Z, ControlModifier)
  2. MainWindow recieves QKeyEvent(KeyRelease, Key_Z, ControlModifier)
  3. QLineEdit recieves QKeyEvent(KeyRelease, Key_Control)
  4. 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 :)

like image 132
Mikhail Churbanov Avatar answered Oct 16 '22 18:10

Mikhail Churbanov