C++ Qt QShortcut with numpad key - c++

QShortcut makes it easy to connect a QShortcutEvent (Key press, combination or sequence) to a slot method, e.g.:
QShortcut *shortcut = new QShortcut( QKeySequence(Qt::Key_7), this, 0, 0, Qt::ApplicationShortcut);
(Hint: for number keys, a QSignalMapper can be used to map the QShortcut's activated() signal to a Slot with int parameter).
However, in this example, with NumLock (numpad enabled), both '7' keys will trigger the Shortcut's activated() signal.
Is there a way to detect the different keys other than filtering or reimplementing a widget's keyPressEvent and check QKeyEvent::modifiers() for Qt::KeypadModifier?
Digging further, I found
QTBUG-20191 Qt::KeypadModifier does not work with setShortcut linking to a patch that has been merged into 4.8 in Sept. 2012 and which comes with a test case using
button2->setShortcut(Qt::Key_5 + Qt::KeypadModifier);
which does not work for my QShortcut on Qt 4.8.1, i.e. neither of the '7' keys are recognized using (adding) the modifier flag.
So I guess the quickest way would be installing a filter to detect the modifier and let all other keyEvents be handled by the default implementation to be useable with QShortcut?

You can use Qt.KeypadModifier, For example [Python]:
def keyPressEvent(self, event):
numpad_mod = int(event.modifiers()) & QtCore.Qt.KeypadModifier
if event.key() == QtCore.Qt.Key5 and numpad_mod:
#Numpad 5 clicked

For this you can use keyReleaseEvent(QKeyEvent *event)
For example
void Form::keyReleaseEvent(QKeyEvent *event) {
int key = event->nativeScanCode();
if( key == 79 ) //value for numpad 7
{
//your statement
}
}

Related

QGraphicsScene, QTextEdit and lost focus

QTextEdit and similar widgets embedded in QGraphicsScene lose focus after using standard context menu (copy/paste), i. e. you need to click on QTextEdit again to continue editing. Scene emits focusItemChanged with newFocusItem==0.
First question: Is it a bug or standard behavior?
My investigation shows that function QGraphicsItemPrivate::setVisibleHelper() clears focus here:
if (hasFocus && scene) {
// Hiding the focus item or the closest non-panel ancestor of the focus item
QGraphicsItem *focusItem = scene->focusItem();
bool clear = true;
if (isWidget && !focusItem->isPanel()) {
do {
if (focusItem == q_ptr) {
clear = !static_cast<QGraphicsWidget *>(q_ptr)->focusNextPrevChild(true);
break;
}
} while ((focusItem = focusItem->parentWidget()) && !focusItem->isPanel());
}
if (clear)
clearFocusHelper(/* giveFocusToParent = */ false, hiddenByPanel);
}
QGraphisItem has undocumented (internal) flag QGraphicsItem::ItemIsFocusScope. If the flag is set for QTextEdit's proxy-item it gets focus back after menu, but in any case focus cleared at first and after that Item receives it again or not.
Second Question: What is flag QGraphicsItem::ItemIsFocusScope for?
Looks like QGraphicsItem::ItemIsFocusScope is for FocusScope QML item. QtQuick1 is QGraphicsScene based and used that flag.
I'm not sure about side effects but that helps:
auto edit = new QLineEdit();
auto item = scene->addWidget(edit);
item->setFlag(QGraphicsItem::GraphicsItemFlag::ItemIsPanel);
Tested on Qt 5.9, Linux
EDIT
For me looks as bug:
add QLineEdit to scene
click to focus QLineEdit
hit ContextMenu key to show context menu
hit Esc key to exit context menu
try to type
Expected: QLineEdit is focused and text appears
Actual: QLineEdit lost input focus
Please find it or report with Qt bug tracker
So it's OK to have workaround using QGraphicsItem::ItemIsFocusScope flag for example.
#if (QT_VERSION < QT_VERSION_CHECK(<fixed in Qt version>))
// it's workaround of bug QTBUG-...
# if (QT_VERSION == QT_VERSION_CHECK(<version you are develop with>)
item.setFlag(QGraphicsItem::ItemIsFocusScope);
# else
# error("The workaround is not tested on this version of Qt. Please run tests/bug_..._workaround_test")
# endif

How to reset QLineEdit text by pressing Escape key?

I'm working on a Qt4 project. I have a QLineEdit and I want to re-use behavior that I see when I click the Escape key inside the QLineEdit, but I'm not sure how.
When I press the escape key, I get the field to reset to the value that it was before I started editing. This is useful to me and I want this behavior on-hand.
Ideally, I would like a signal I can fire off that triggers the QLineEdit to reset to the value it was before. I would prefer not to try and fake an escape key event. I can cache the old value of the line edit, but this seems more work if the behavior already exists when I click escape. Thanks.
How can I make QLineEdit widget to respond on Escape key by setting
the text programmatically?
Either by overriding QWidget::event virtual function with the child of QLineEdit or a bit more "local", like installing the event filter:
class MyLineEditEventFilter : public QObject
{
public:
explicit MyLineEditEventFilter(QLineEdit *parent) : QObject(parent)
{}
bool eventFilter(QObject *obj, QEvent *e)
{
switch (e->type())
{
case QEvent::KeyPress:
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (keyEvent->key() == Qt::Key_Escape)
{
// or set the other text from the variable
reinterpret_cast<QLineEdit *>(parent())->setText("Escape!");
}
break;
}
}
// standard event processing
return QObject::eventFilter(obj, e);
}
};
And the caller is like that:
m_pLineEditSearch = new QLineEdit;
auto* pLineEditEvtFilter = new MyLineEditEventFilter(m_pLineEditSearch);
m_pLineEditSearch->installEventFilter(pLineEditEvtFilter);
For getting the previous text to reset with Escape pressed you may use different methods but you need to obtain the pointer to the object holding the string somehow. That is hard to answer without seeing your code.
You can also use QDataWidgetMapper and map your QLineEdit to its model. The Esc key behavior you're mentioning is already built-in there. You can also take advantage of the other functionalities that QDataWidgetMapper provides.
Here's how you would use it:
//Your model can have multiple rows and columns, let's assume it's just one
//QLineEdit, that will mean one row and one column
QStandardItemModel *model = new QStandardItemModel(1,1,this);
QStandardItem* item = new QStandardItem("QLineEdit initial value");
QLineEdit* myLineEdit = new QLineEdit(this);
QDataWidgetMapper* dataWidgetMapper = new QDataWidgetMapper(this);
dataWidgetMapper->setModel(model);
dataWidgetMapper->addMapping(myLineEdit, 0);
dataWidgetMapper->toFirst();
Hope this helps.
UPDATE:
There have been some valid concerns raised in the comments under my section about some of the pitfalls of QDataWidgetMapper, namely the fact that it not only responds to Escape key but also other keys like Enter/Return. So what happens is that after calling QDataWidgetMapper::addMapping(QWidget*, int) an event filter will be installed on your widget, which will consume Escape and Enter/Return keys. If you are still interested in finding out whether Enter or Escape have been pressed, you can add one more event filter right after that where you can check for other keys pressed. I have tried it and it works.

Qt. QSplitter. Handle double click when cursor is under splitter

I need to handle double click for QSplitter when cursor is under splitter.
I redefined mouseDoubleClickEvent. But this does not work for this case.
When I do doulble click when cursor is under splitter (ready to move splitter) the method is not calling.
You can use an event filter to filter all events going to the handle of Qsplitter :
bool MyClass::eventFilter(QObject * obj, QEvent * event)
{
if(event->type()==QEvent::MouseButtonDblClick)
{
...
}
return false;
}
Also don't forget to install event filter in the constructor of your class :
MyClass::MyClass(QWidget *parent):QWidget(parent)
{
...
ui->splitter->handle(1)->installEventFilter(this);
...
}
I needed the same in order to be able to evenly space the widgets in the splitter when the user double clicks on the handle (this was my use case). Overriding QSplitter.mouseDoubleClickEvent() does not work because it seems that the handle consumes the double click event itself so it is not propagated to the parent QSplitter. The solution proposed in the accepted answer using eventFilter is quite good but is has the disadvantage that it is not 'dynamic', i.e. the event filter is not installed when the user adds new widgets to the splitter during runtime. So we need to find a way to install the event filter dynamically. There are two options to achieve this:
Override QSplitter.addWidget() and QSplitter.insertWidget():
# inside class MySplitter
def addWidget(self, widget):
super(MySplitter, self).addWidget(widget) # call the base class
self.handle(self.count() - 1).installEventFilter(self)
def insertWidget(self, index, widget):
super(MySplitter, self).insertWidget(index, widget) # call the base class
self.handle(index).installEventFilter(self)
but this is a bit problematic when the user adds widgets by not using these two methods but by setting parent to the child widget, though this is discouraged by the docs - see: http://doc.qt.io/qt-5/qsplitter.html#childEvent
Intercept the childEvent(), which feels a bit hacky but is error proof:
# inside class MySplitter
def childEvent(self, event):
if event.added():
# Note that we cannot test isinstance(event.child(), QSplitterHandle)
# because at this moment the child is of type QWidget,
# it is not fully constructed yet.
# So we assume (hacky but it works) that the splitter
# handle is added always as the second child after
# the true child widget.
if len(self.children()) % 2 == 0:
event.child().installEventFilter(self)
super(MySplitter, self).childEvent(event) # call the base class
I am using b) and it works for me quite well. This has the advantage that you do not need to subclass (I do, however, for teh sake of simplicity), you can install another event filter to intercept the childEvent and install the event filter from outside.
Sorry my code is in PyQt, but I think it is idiomatic enough and easily translated to C++.

Fail to clear QLineEdit after selecting item from QCompleter

using PopupCompletion mode when you select an item (using arrow keys) and press return - lineEdit should become empty (i clear lineEdit when return is pressed), but lineEdit does not become empty. (If you press 'Enter' again it will empty the lineEdit). So i think pressing return does clear lineEdit, but pressing return also tells QCompleter to insert selected item into lineEdit, so it seems like nothing happens.
But, if you click the item insted of selecting it with arrows - everything works fine.
I tried to find the solution on the internet, but i found only one person that had the same problem: http://lists.trolltech.com/qt-interest/2006-10/thread00985-0.html . Sadly there are no answers. Please read his question because it will help understand my problem.
How can I clean LineEdit after QCompleter inserted selected item? (catching activated signal does not help)
The issue here is that the completer actually contains a pop-up, which is actually a separate QAbstractItemView widget (refer to the QCompleter::popup() documentation). As such, when you press 'Enter' on the QCompleter, the key event actually goes to the pop-up and not the line edit.
There are two different ways to resolve your issue:
Option 1
Connect the completer's activated signal to the line edit's clear slot, but do it as a QueuedConnection:
QObject::connect(completer, SIGNAL(activated(const QString&)),
lineEdit, SLOT(clear()),
Qt::QueuedConnection);
The reason why using a direct connection doesn't work is because your are essentially dependent on the order in which slots get called from a signal. Using a QueuedConnection gets around this. From a code maintenance standpoint, I don't really prefer this solution because it isn't clear what your intention is just by looking at the code.
Option 2
Write an event filter around the pop-up to filter out the 'Enter' key to clear the line edit explicitly. Your event filter would end up looking something like this:
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter(QLineEdit* lineEdit, QObject* parent = NULL)
:QObject(parent)
,mLineEdit(lineEdit)
{ }
virtual ~EventFilter()
{ }
bool eventFilter(QObject* watched, QEvent* event)
{
QAbstractItemView* view = qobject_cast<QAbstractItemView*>(watched);
if (event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Return ||
keyEvent->key() == Qt::Key_Enter)
{
mLineEdit->clear();
view->hide();
return true;
}
}
return false;
}
private:
QLineEdit* mLineEdit;
};
You would then install the event filter on the completer's pop-up:
EventFilter* filter = new EventFilter(lineEdit);
completer->popup()->installEventFilter(filter);
This option is more work, but it's clearer as to what you are doing. Moreover, you can perform additional customization this way, if you prefer.

Qt multiple key combo event

I'm using Qt 4.6 and I'd like to react to multi-key combos (e.g. Key_Q+Key_W) that are being held down. So when you hold down a key combo, the event should be called all the time, just the same way as it works with single key events. I tried to use QShortcuts and enable autorepeat for them, but that didn't work:
keyCombos_.push_back(new QShortcut(QKeySequence(Qt::Key_W, Qt::Key_D), this));
connect(keyCombos_[0], SIGNAL(activated()), SLOT(keySequenceEvent_WD()));
setShortcutAutoRepeat(keyCombos_[0]->id(), true);
When using this approach I also have the problem that I can't catch single Key_W (or whatever the first Key in the keysequence is) strokes anymore.
Thanks,
Thomas
You can add a pressed key to the set of pressed keys and remove from this set when the key is released. So you can add the pressed key to a QSet which is a class member :
QSet<int> pressedKeys;
You can catch the key events in an event filter :
bool MyWidget::eventFilter(QObject * obj, QEvent * event)
{
if ( event->type() == QEvent::KeyPress ) {
pressedKeys += ((QKeyEvent*)event)->key();
if ( pressedKeys.contains(Qt::Key_D) && pressedKeys.contains(Qt::Key_W) )
{
// D and W are pressed
}
}
else if ( event->type() == QEvent::KeyRelease )
{
pressedKeys -= ((QKeyEvent*)event)->key();
}
return false;
}
Don't forget to install the event filter in the constructor:
this->installEventFilter(this);
QShortcut does not support the functionality you're looking for. You can only make combinations with modifier keys like Shift, Ctrl, Alt and Meta.
What your code does is to make a shortcut that responds when the user first presses W and then D. This is also why it will conflict with other shortcuts that respond to just W.
When you want to do something when both W and D are pressed at the same time, you'll have to override QWidget's keyPressEvent and keyReleaseEvent methods in order to keep track of their pressed state, and manually call your handler function once they are both pressed. If you don't have a suitable QWidget subclass in use you'd either have to introduce it, or install an event filter in the right place using QObject::installEventFilter, possibly on your application object if it's supposed to be a global shortcut.