Qt multiple key combo event - c++

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.

Related

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.

MFC) Making TreeCtrl loses its item focus

I have used TVN_SELCHANGED message to find out what user select in item tree (Menu).
However, if user continually click same item, that message does not occur.
I want treeCtrl to lose its item selection for occurring TVN_SELCHANGED.
(In other words, I want to make event happened even if user click same item consecutively)
How do I do that?
TVN_SELCHANGE will not help. Nothing is changed, so the notification isn't sent. Even it makes no sense for me. What should a UI do, if a user clicks on an already selected item? Nothing... I would guess.
If you want to handle this, you have to do it by yourself.
You can use WM_LBUTTONDOWN or NM_CLICK, to track the click.
Than use TVM_HITTEST to check what was clicked by the user.
Now you can compare the current selection (TVM_GETNEXTITEM and check for TVGN_CARET)
compare old and new selection.
After all, pass the click to the default handler.
The only time the TreeCtrl will get notified when an item is selected is: TVN_SELCHANGE. In case of same selection, this won't help. But there is another way to get notified.
Add PreTranslateMessage command in your dialog class where TreeCtrl is used and add the code written below.
//---------------------------------------------------------------------------
BOOL MyDlgClass::PreTranslateMessage(MSG* pMsg)
{
UINT msgValue = pMsg->message;
//here I have compared L button down event, you can use any
//mouse/keyboard event that you want to compare.
if (msgValue == WM_LBUTTONDOWN)
{
CPoint point;
point.x = (int)(short)LOWORD(pMsg->lParam);
point.y = (int)(short)HIWORD(pMsg->lParam);
OnLButtonDown(pMsg->message, point);
}
}
void MyDlgClass::OnLButtonDown(UINT nType, CPoint point)
{
UINT uFlags;
HTREEITEM hItem = m_treeCtrl.HitTest(point, &uFlags);
if ((hItem != NULL) && (TVHT_ONITEMBUTTON & uFlags))
{
return;
}
//TVHT_ONITEMBUTTON detects if user has clicked + or - button of tree
//view.
//Add code to perform your operations on hItem.
}

C++ Qt QShortcut with numpad key

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
}
}

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.

how to use ribbon category to switch view?

I want to use two ribbon category buttons to switch between two different views, but I found it can't add event handler to the button.
Is there any hint to solve this problem? Better if there is some sample, actually I'm new to MFC.
You could try hooking into this event?
AFX_WM_ON_CHANGE_RIBBON_CATEGORY
An option I have found successful was to subclass CMFCRibbonBar and override PreTranslateMessage and check for mouse clicks. Below are the steps I took which have thus far worked well.
Subclass CMFCRibbon - in my example I created CCustomRibbonBar
Override PreTranslateMessage, and add an int value to keep track of the tab
Create a custom windows message that your applications MainForm handles -WM_ACTIVE_RIBBON_TAB_CHANGED in my example
Inside of PreTranslateMessage check for Left Mouse Up event
In the event of a left mouse button up, let the Ribbon finish handling the message and then query the Active Category.
Post the active category to MainForm (or other form)
In your MainForm, handle the category and take into account that with most events the category will not have changed.
Then in my override I check for the mouse up event and retrieve the Active category
Inside Class Declaration
virtual BOOL PreTranslateMessage(MSG* pMsg);
int m_LastActiveCategory;
Inside Class Definition
BOOL CCustomRibbonBar::PreTranslateMessage(MSG* pMsg)
{
//If command was finishing a click
if(pMsg->message == WM_LBUTTONUP && pMsg->wParam == 0)
{
//Allow ribbon to handle itself first
BOOL result = CMFCRibbonBar::PreTranslateMessage(pMsg);
//Get new active tab
int activeTab = GetCategoryIndex(GetActiveCategory());
//If tab has changed, forward message
if(activeTab != m_LastActiveCategory)
{
//forward message to parent
::SendMessage(GetParentFrame()->GetSafeHwnd(),WM_ACTIVE_RIBBON_TAB_CHANGED,activeTab,0);
m_LastActiveCategory = activeTab;
}
return result;
}
//Otherwise handle like normal
return CMFCRibbonBar::PreTranslateMessage(pMsg);
}