Preventing key events - c++

I have a simple application with just one QPlainTextEdit, basically the same as Qt's example here:
http://qt-project.org/doc/qt-5.1/qtwidgets/mainwindows-application.html
When I press Ctrl+Z, it calls undo. When I press Ctrl+A, it selects all text. This is ok.
But when I press Ctrl+E or Ctrl+R, which are not defined in the menu, the characters "e" and "r" will appear in the QSimpleTextEdit.
How do I prevent this? How to "filter" keypresses which have been defined as menu shortcuts and keep them working, and "prevent" those keypresses not defined as menu shortcuts from appearing in the edit?

There are 2 options:
1) Create a subclass and reimplement keyPressEvent()
2) Create an eventFilter and use installEventFilter() (see http://qt-project.org/doc/qt-5.0/qtcore/qobject.html#installEventFilter)

You can use the following code:
CSimpleEdit.h
#include <QPlainTextEdit>
class CSimpleEdit: public QPlainTextEdit
{ Q_OBJECT
public:
explicit CSimpleEdit(QWidget* parent = 0);
~ CSimpleEdit();
protected:
bool eventFilter(QObject* obj, QEvent* event);
};
CSimpleEdit.cpp
CSimpleEdit::CSimpleEdit(QWidget* parent)
: QPlainTextEdit(parent)
{ installEventFilter(this); }
CSimpleEdit::~CSimpleEdit()
{ removeEventFilter(this); }
bool CSimpleEdit::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->modifiers() != Qt::NoModifier &&
!keyEvent->modifiers().testFlag(Qt::ShiftModifier))
{ bool bMatch = false;
for (int i = QKeySequence::HelpContents; i < QKeySequence::Deselect; i++)
{ bMatch = keyEvent->matches((QKeySequence::StandardKey) i);
if (bMatch) break;
}
/*You can also set bMatch to true by checking you application
*actions list -> QWidget::actions(). */
if (!bMatch) return true;
}
}
else if (event->type() == QEvent::KeyRelease)
{ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->modifiers() != Qt::NoModifier &&
!keyEvent->modifiers().testFlag(Qt::ShiftModifier))
{ bool bMatch = false;
for (int i = QKeySequence::HelpContents; i < QKeySequence::Deselect; i++)
{ bMatch = keyEvent->matches((QKeySequence::StandardKey) i);
if (bMatch) break;
}
if (!bMatch) return true;
}
}
return QObject::eventFilter(obj, event);
}

Related

Double triggering QEvent::KeyPress and QEvent::KeyRelease

I have a problem: I'm trying to catch a keystroke.
I use eventFilter:
bool Inf_int_qt::eventFilter(QObject *obj, QEvent *event)
{
// ::SoundKeys(event);
this->SoundKeys(event);
return true;
}
Next I do the following checks:
void Inf_int_qt::SoundKeys(QEvent* event)
{
if(event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
{
bool p_press = event->type() == QEvent::KeyPress;
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if( keyEvent->isAutoRepeat() )
return;
switch (keyEvent->key())
{
case Qt::Key_Control:
//do something
break;
}
}
}
In the end, when I quickly press (and release) the ctrl button, I get as many as 4 QEvent triggers:
QEvent::KeyPress
QEvent::KeyPress
QEvent::KeyRelease
QEvent::KeyRelease
It turns out that each signal is generated twice. I can't understand anything. Why can it work like this? OC Win 10. Qt 5.8.0

Receive Escape-Event in QLineEdit?

This is a bit of a beginners question but I don't find the solution.
I'm using an own object that inherits from QLineEdit and reiceves numbers as input (which works smoothly now).
Now I want to receive an event, when the user presses the Escape-button. This does not happen with the textChanged()-event. According to the documentation there is no special escape-event. So how else can this be done?
Thanks!
I had this same problem. I am solving it by implementing keyPressEvent in my QMainWindow.
void MainWindow::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Escape) {
QLineEdit *focus = qobject_cast<QLineEdit *>(focusWidget());
if (lineEditKeyEventSet.contains(focus)) {
focus->clear();
}
}
}
And setting up QSet<QLineEdit *> lineEditKeyEventSet to contain the QLineEdits that need this behavior.
void MainWindow::setupLineEditKeyEventList()
{
lineEditKeyEventSet.insert(ui->lineEdit_1);
lineEditKeyEventSet.insert(ui->lineEdit_2);
lineEditKeyEventSet.insert(ui->lineEdit_3);
}
You can either implement keyPressEvent :
void LineEdit::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape)
{
...
}
QLineEdit::keyPressEvent(event);
}
Or implement eventFilter :
bool LineEdit::eventFilter(QObject *obj, QEvent * event)
{
if((LineEdit *)obj == this && event->type()==QEvent::KeyPress && ((QKeyEvent*)event)->key() == Qt::Key_Escape )
{
...
}
return false;
}
When using the eventFilter approach, install the event filter in the constructor :
this->installEventFilter(this);

How to disable control+A shortcut for QTableView and prevent select all option?

I want to prevent control+A shortcut from selecting all the files in QTableView, I want to make it as such that it can only select with mouse but not with a keyboard shortcut. Right now my eventFilter code is as below. Could you please suggest me a way to do that?
bool MainWindow::eventFilter(QObject* obj, QEvent *ev)
{
if(ev->type() == QEvent::MouseButtonPress)
{
if(obj == ui->listOfImages->viewport())
{
QMouseEvent * mouseEv = static_cast<QMouseEvent*>(ev);
if((mouseEv->buttons() & Qt::LeftButton) && (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == true))
{
controlButtonCounter++;
fetch = true;
return QObject::eventFilter(obj,ev);
}
else if((mouseEv->buttons() & Qt::LeftButton) && (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == false))
{
if(selectedImages.size()>0)
{
ui->listOfImages->clearSelection();
selectedImages.clear();
selectedList.clear();
ui->selectedFiles->clear();
ui->selectedFiles->show();
}
fetch = false;
controlButtonCounter = 0;
}
}
}
return QObject::eventFilter(obj,ev);
}
I would try to extend your event filter's code with the following block:
[..]
if (event->type() == QEvent::KeyPress && obj == ui->listOfImages->viewport()) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
int key = keyEvent->key();
Qt::KeyboardModifiers modifiers = keyEvent->modifiers();
if(modifiers & Qt::ShiftModifier)
key += Qt::SHIFT;
if(modifiers & Qt::ControlModifier)
key += Qt::CTRL;
if(modifiers & Qt::AltModifier)
key += Qt::ALT;
if(modifiers & Qt::MetaModifier)
key += Qt::META;
if (QKeySequence(key) == QKeySequence(QKeySequence::SelectAll)) {
// Filter the event.
return true;
}
}
[..]
This code is supposed to return true (filter the event) if standard "select all" key combination pressed. It is usually Ctrl+A keys.

Why pressing of "Tab" key emits only QEvent::ShortcutOverride event?

Background
I've made a custom widget with QLineEdit and several QPushButtons to use it with custom item delegate:
class LineEditor : public QWidget
{
public:
explicit LineEditor(QWidget *parent = 0) : QWidget(parent) {
setLayout(new QHBoxLayout);
layout()->setContentsMargins(0, 0, 0, 0);
layout()->setSpacing(0);
QLineEdit *edit = new QLineEdit(this);
layout()->addWidget(edit);
layout()->addWidget(new QPushButton(this));
layout()->addWidget(new QPushButton(this));
setFocusProxy(edit);
}
};
class PropertyDelegate : public QItemDelegate
{
public:
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
return new LineEditor(parent);
}
bool eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
qDebug() << "KeyPress";
}
if (event->type() == QEvent::ShortcutOverride) {
qDebug() << "ShortcutOverride";
}
return QItemDelegate::eventFilter(object, event);
}
};
I'm going to bind them with QListView and QStandardItemModel like this:
QStandardItemModel *model = new QStandardItemModel;
model->appendRow(new QStandardItem("1"));
model->appendRow(new QStandardItem("2"));
model->appendRow(new QStandardItem("3"));
QListView w;
w.setItemDelegate(new PropertyDelegate);
w.setModel(model);
w.show();
Question
Why in the PropertyDelegate::eventFilter when Tab key is pressed there is only QEvent::ShortcutOverride event, but pressing of any other key emits both QEvent::ShortcutOverride and QEvent::KeyPress events?
UPD: I want to implement the moving between lines by pressing Tab and Backtab like with standard widgets.
Well, finally I've done some research about that.
Explanation
When a view calls createEditor function of delegate it also installs the delegate event filter to editor.
QWidget *QAbstractItemViewPrivate::editor(const QModelIndex &index,
const QStyleOptionViewItem &options)
{
Q_Q(QAbstractItemView);
QWidget *w = editorForIndex(index).widget.data();
if (!w) {
QAbstractItemDelegate *delegate = delegateForIndex(index);
if (!delegate)
return 0;
w = delegate->createEditor(viewport, options, index);
if (w) {
w->installEventFilter(delegate);
......
}
However the delegate can catch only events of editor widget, but not events of its children. When the Tab key is pressed the QWidget::event function is called, it uses it to change focus to another widget:
bool QWidget::event(QEvent *event)
{
......
switch (event->type()) {
......
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
......
}
......
}
Accordingly in my case the focus is set to next QPushButton after QLineEdit and event isn't propagated to the parent (LineEditor).
Solving
The right way to solve the problem is like QSpinBox does it. Because it is also has QLineEdit. In constructor of widget it sets focus proxy for line edit:
edit->setFocusProxy(this);
So all of focus events will reach the main widget. Also the focusPolicy property must be set because it's NoFocus by default:
setFocusPolicy(Qt::WheelFocus);
All we need to do in this moment it is to propagate necessary events to QLineEdit from main widget like this:
bool LineEditor::event(QEvent *e)
{
switch(e->type())
{
case QEvent::ShortcutOverride:
if(m_lineEdit->event(e))
return true;
break;
case QEvent::InputMethod:
return m_lineEdit->event(e);
default:
break;
}
return QWidget::event(e);
}
void LineEditor::keyPressEvent(QKeyEvent *e)
{
m_lineEdit->event(e);
}
void LineEditor::mousePressEvent(QMouseEvent *e)
{
if(e->button() != Qt::LeftButton)
return;
e->ignore();
}
void LineEditor::mouseReleaseEvent(QMouseEvent *e)
{
e->accept();
}
void LineEditor::focusInEvent(QFocusEvent *e)
{
m_lineEdit->event(e);
QWidget::focusInEvent(e);
}
void LineEditor::focusOutEvent(QFocusEvent *e)
{
m_lineEdit->event(e);
QWidget::focusOutEvent(e);
}
This should be enough.
Tricky
As it's said above the delegate can't catch events of editor's children. So to make editor's behavior like "native" I have to duplicate events from children to editor.
LineEditor installs event filter to QLineEdit in constructor:
edit->installEventFilter(this);
Implementation of filter looks like this:
bool LineEditor::eventFilter(QObject *object, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab)
{
QApplication::postEvent(this, new QKeyEvent(keyEvent->type(), keyEvent->key(), keyEvent->modifiers()));
// Filter this event because the editor will be closed anyway
return true;
}
}
else if(event->type() == QEvent::FocusOut)
{
QFocusEvent* focusEvent = static_cast<QFocusEvent *>(event);
QApplication::postEvent(this, new QFocusEvent(focusEvent->type(), focusEvent->reason()));
// Don't filter because focus can be changed internally in editor
return false;
}
return QWidget::eventFilter(object, event);
}
It's also possible to use qApp->notify(this, event) for QKeyEvent instead of QApplication::postEvent because we filter this events anyway. But it's not possible for QFocusEvent because notify will redirect the event and it will not reach a child.
Note that the standard (QItemDelegate or QStyledItemDelegate) delegate will care about situation when focus is changed internally by itself:
if (event->type() == QEvent::FocusOut || (event->type() == QEvent::Hide && editor->isWindow())) {
//the Hide event will take care of he editors that are in fact complete dialogs
if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) {
QWidget *w = QApplication::focusWidget();
while (w) { // don't worry about focus changes internally in the editor
if (w == editor)
return false;
w = w->parentWidget();
}
......

QComboBox EventFilter to popup

I have a little problem, I need to set my event filter to QComboBox popup.
I need to catch events when left and right keys are pressed.
How can I do this?
Thank you!
You need to set the eventFilter on QComboBox's view() (http://qt-project.org/doc/qt-4.8/qcombobox.html#view).
You may need to add following code somewhere in your code.
void MyComboBox::keyPressEvent (QKeyEvent *event)
{
if (event->button() == Qt::Key_Left)
{
// handle left key press
}
if (event->button() == Qt::Key_Right)
{
// handle right key press
}
}
Hope this helps!
The question is quite old, but I provide my answer since it can help someone else.
After popup all events will be sent to the list view used for the QComboBox popup. You can get the things done using key handler class watching on events for list view.
KeyPressHandler.h:
class KeyPressHandler : public QObject
{
Q_OBJECT
public:
explicit KeyPressHandler(QObject *parent = nullptr);
virtual ~KeyPressHandler() override;
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
KeyPressHandler.cpp:
#include <QCoreApplication>
KeyPressHandler::KeyPressHandler(QObject *parent) : QObject(parent)
{
}
KeyPressHandler::~KeyPressHandler()
{
}
bool KeyPressHandler::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch(keyEvent->key())
{
case Qt::Key_Left:
// Send press event for the Key_Up which understood by list view
QCoreApplication::postEvent(obj, new QKeyEvent(QEvent::KeyPress,
Qt::Key_Up,
Qt::NoModifier));
return true;
case Qt::Key_Right:
QCoreApplication::postEvent(obj, new QKeyEvent(QEvent::KeyPress,
Qt::Key_Down,
Qt::NoModifier));
return true;
default:
break;
}
}
// standard event processing
return QObject::eventFilter(obj, event);
}
In ComboBox you will need to install event filter when popup is shown.
It can be done in different ways, for example by overriding QComboBox::showPopup() function.
MyComboBox.h:
#include <memory>
#include <QComboBox>
class MyComboBox : public QComboBox
{
Q_OBJECT
public:
explicit MyComboBox(QWidget *parent = 0);
protected:
void showPopup() override;
void hidePopup() override;
private:
std::unique_ptr<KeyPressHandler> m_key_press_handler;
};
MyComboBox.cpp:
...
void MyComboBox::showPopup()
{
if(!m_key_press_handler)
{
m_key_press_handler.reset(new KeyPressHandler());
QAbstractItemView *v = view();
v->installEventFilter(m_key_press_handler.get());
}
QComboBox::showPopup();
}
void MyComboBox::hidePopup()
{
m_key_press_handler.reset(nullptr);
QComboBox::hidePopup();
}