How to reset QLineEdit text by pressing Escape key? - c++

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.

Related

How to use a custom validation function for a QCompleter in a QComboBox

I have a string matching function to be used for searching for names that is more advanced than QString::contains() (e. g. when you search for "mueller", it will match "Müller").
I'd like to use this function to search inside a QComboBox. The default completion almost does what I need: If I do
combobox->setEditable(true);
combobox->setInsertPolicy(QComboBox::NoInsert);
combobox->completer()->setCompletionMode(QCompleter::PopupCompletion);
and type some text in the QComboBox's lineedit, the popup pops up, only showing entries starting what has been typed.
This is what I want, but I would like the QCompleter to evaluate matches using my search function rather than the QString::startsWith() that is apparently used here (and setting the mode to Qt::MatchContains is better but still not sufficient).
Is there any way to customize the completer's search function?
Thanks for all help!
I ended up using an own QCompleter and set it for the QComboBox's QLineEdit. The completer does not use the combobox's model, but an own one, which is filled with data everytime the entered text changes.
Can be done as follows:
m_matchingNames = new QStringListModel(this);
m_nameCompleter = new QCompleter(m_matchingNames, this);
m_nameCompleter->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
m_playersSelect->setEditable(true);
m_playersSelect->setInsertPolicy(QComboBox::NoInsert);
m_playersSelect->setCompleter(0);
m_playersSelect->lineEdit()->setCompleter(m_nameCompleter);
connect(m_playersSelect->lineEdit(), &QLineEdit::textEdited, this, &ScorePage::nameSearchChanged);
and
void ScorePage::nameSearchChanged(const QString &text)
{
QStringList possibleNames;
for (const QString &name : m_availableNames) {
if (checkMatch(name, text)) {
possibleNames << name;
}
}
m_matchingNames->setStringList(possibleNames);
}
Most probably not the most prerformant solution, but it works :-)
One then can also connect to QCompleter::activated() to process what has been chosen from the list and e. g. do a QComboBox::setCurrentIndex() or such.

QAbstractItemView Tab Focus While Editing Item

I have a QTreeView populated with items from a model. When a call to edit() is made on an index, a custom editor displays. The editor consists of two QLineEdit widgets.
I want the focus to switch between the two QLineEdit widgets when Tab is pressed. However, pressing Tab cycles through everything else on my program. All my QPushButton and QTabWidget objects are included in the Tab order even though they are completely different widgets than my editor.
I've tried setting the tab order using setTabOrder() to loop it between the two QLineEdit widgets, however this still doesn't isolate the editor widget from the surrounding widgets. Why is this happening?
NOTE: I'm not trying to disable tab ordering anywhere else, just isolate it to my editor for the time being.
Thanks for your time!
This can be easily implemented using QWidget::focusNextPrevChild as follows:
class EditWidget : public QWidget
{
public:
EditWidget(QWidget *pParent) : QWidget(pParent)
{
QHBoxLayout *pLayout = new QHBoxLayout(this);
setLayout(pLayout);
pLayout->addWidget(m_pEdit1 = new QLineEdit ());
pLayout->addWidget(m_pEdit2 = new QLineEdit ());
}
bool focusNextPrevChild(bool next)
{
if (m_pEdit2->hasFocus())
m_pEdit1->setFocus();
else
m_pEdit2->setFocus();
return true; // prevent further actions (i.e. consume the (tab) event)
}
protected:
QLineEdit *m_pEdit1;
QLineEdit *m_pEdit2;
};

QTreeWidget editItem fails with "edit: editing failed"

I have a QTreeWidgetItem added to a QTreeWidget:
QTreeWidgetItem* item = new QTreeWidgetItem(ui->trwPairs);
item->setFlags(item->flags() | Qt::ItemIsEditable);
If the item is edited, I want to do a few checks on the new value:
Pairs::Pairs(QWidget *parent) :
QWidget(parent),
ui(new Ui::Pairs)
{
ui->setupUi(this);
connect(this->ui->trwPairs, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(Validate(QTreeWidgetItem*,int)));
}
void Pairs::Validate(QTreeWidgetItem* item, int column)
{
if (item->text(column).toInt() < 1)
{
QMessageBox::critical(this, "Error", QString("Node ID ") + item->text(column) + " is invalid.");
ui->trwPairs->editItem(item, column);
}
}
Naturally, if it's less than 1, it catches it, and gives me the message box. However, printed to cerr is edit: editing failed and the item is not in edit mode. What am I missing?
Stepping through it in the debugger reveals the following:
In quabstractitemview.cpp line false is returned on line 3953. Somehow it looks like your item is still in editing state and you are trying to edit it again or something.
bool QAbstractItemViewPrivate::shouldEdit(QAbstractItemView::EditTrigger trigger,
const QModelIndex &index) const
{
// ..
if (state == QAbstractItemView::EditingState)
return false;
}
IIRC I had a similar problem with tables with multiple lines per cell. Check out the classes QAbstractItemDelegate views have item delegates which allow you to control which editor is used and how it behaves. I believe by default the QLineEdit is used. Editors like QLineEdit can have validators which control how the data is validated, in your case reject it if the numerical value is < 0. But I think you have to use the model / view classes and implement your own model for that. The Qt documentation for QTreeWidget::setItemWidget(..) says:
This function should only be used to display static content in the place of a tree widget item. If you want to display custom dynamic content or implement a custom editor widget, use QTreeView and subclass QItemDelegate instead.
I am not sure however if there is a simpler way to do this using the widget classes.
The problem could be, that you are setting the flags for your items in a very strange way.
Simply enable both item-selection, and edit:
item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
I had a similar issue where I was attempting to edit the subsequent column upon receiving the itemChanged signal. Based on Nils' analysis that the item was still in the edit state, I changed the signal connection type to QueuedConnection, which allowed the item to leave the state before re-entering it.
I had a similar problem where I'd get the 'edit: editing failed' error when invoking edit() via a shortcut key. I was passing currentIndex() to edit(), but I wasn't checking that the correct column of the selected row was current. I only had the first column editable, so if I had clicked the row (but in any other column) and then invoked my edit key I'd get the error.
I was able to solve my problem by passing the result of sibling(currentIndex().row(), 0) to edit() instead.

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.