Fail to clear QLineEdit after selecting item from QCompleter - c++

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.

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.

How to avoid mouse click on one widget triggering the signals in others in Qt?

In my app I'm using a QTableView to show a list of images and I select some of the images by clicking left mouse button and pressing control keyboard button when I do so the app looks as the below stated image:
But then when I try to use other buttons on the app like "Destination" and then try to select a destination folder then the app looks like this below:
Problem occurs when I click the "select folder" button and try to select the folder. What happens is that click on the folder selection tab, triggers QTableView widget in which I show the image and the deselects all the selected images. I want to avoid it. The way I now track the left mouse button clicks on QTableView widget is as below:
bool MainWindow::eventFilter(QObject* obj, QEvent *ev)
{
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();
}
fetch = false;
controlButtonCounter = 0;
}
}
return QObject::eventFilter(obj,ev);
}
Here ui->listOfImages is the QTableView widget. Other things like controlButtonCounter are irrelevant in taking the mouse clicks, I use it for other purposes.
Please say me how I can avoid triggering QTableView widget when I'm pressing on other things that fall in the same region as the QTableView.
if(obj = ui->listOfImages->viewport())
You are not doing a comparison there, you are assigning a value to the obj variable.
It should be like this:
if(obj == ui->listOfImages->viewport())
I'm not sure but maybe it can help you:
void setWindowModality(Qt::WindowModality windowModality)
This property holds which windows are blocked by the modal widget.
This property only makes sense for windows. A modal widget prevents widgets in other windows from getting input. The value of this property controls which windows are blocked when the widget is visible. Changing this property while the window is visible has no effect; you must hide() the widget first, then show() it again.
By default, this property is Qt::NonModal.

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.

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.