Remove editable item from QListWidget if empty - c++

I'm using Qt 4.8.6.
I have a QListWidget. When the user click on an Add button, a new item is inserted at the end of the list and edition of the item's text is initiated:
void slot_add_item()
{
auto* item = new QListWidgetItem(QString());
item->setFlags(item->flags() | Qt::ItemIsEditable);
listWidget->addItem(item);
listWidget->setCurrentItem(item);
listWidget->editItem(item);
}
Based on this comment, I'm listening to the commitData signal to catch when the user has finished editing the item and to remove it if the item's text is empty:
connect(
listWidget->itemDelegate(), SIGNAL(commitData(QWidget*)),
SLOT(slot_item_edited(QWidget*)));
...
void slot_item_edited(QWidget* widget)
{
const QString path = reinterpret_cast<QLineEdit*>(widget)->text();
if (path.isEmpty())
delete listWidget->currentItem();
}
However that doesn't catch the case where the user cancels editing with the Escape key: in that case, slot_item_edited() is not called (expectedly) and the (empty) item is not removed.
Any idea on how to remove the item in that case?

You can connect to closeEditor signal of the delegate instead of commitData signal: closeEditor signal is emitted when the editor is closed regardless of the fact whether any new data was entered into the model or not.

Related

QTreeView: how to abort selection change

I have a QTreeView in my widget. When an item is selected in the view, I have
a signal handler that updates a series of information widgets in a detail window
about the selected item. The user can then edit the item details and commit the
changes back to the model.
If the data in the details view has been edited when this selection change
happens, I present a confirmation dialog to the user before replacing the data
when a new item is selected. If the user cancels, I want to set the selection of
the tree back to what it was before.
I have my slot connected like so:
auto selection_model = treeview->selectionModel();
connect(
selection_model, &QItemSelectionModel::currentChanged,
this, &Editor::on_tree_selection_changed
)
Inside my slot, the code is structured as follows:
void on_tree_selection_changed(QModelIndex const& index, QModelIndex const& previous)
{
if(not confirm_editor_discard())
{
// user does not want to override current edits
log.trace("cancel item selection change");
using SF = QItemSelectionModel::SelectionFlags;
auto sm = treeview->selectionModel();
sm->setCurrentIndex(previous, SF::SelectCurrent | SF::Rows);
}
else
{
// user wants to discard, so update the details view.
log.trace("discard pending edits");
set_details_from_model(index);
}
}
However, the setting of the current index back to the previous does not seem to
affect the TreeView; it still displays the newly selected item as selected, and
the interface becomes non-coherent since the item displayed in the details is
not the one shown as selected in the tree.
The intended behaviour is to re-select the previously selected item, as if no
new selection was made at all.
Apparently the QTreeView ignores any updates from the selection model while the currentChanged slot is being called.
The solution here was to call the slot as a QueuedConnection, so the connect line would look like this:
connect(
selection_model, &QItemSelectionModel::currentChanged,
this, &Editor::on_tree_selection_changed,
Qt::QueuedConnection // <-- connection must be queued.
)
This will ensure that the change in selection of the selection model will not happen directly inside a slot call.

QSortFilterProxyModel how to handle QStandardItems correctly

I have QTreeView with some items and search QLineEdit with connected slot on textEdited signal.
With this code:
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(messagesModel);
proxyModel->setFilterFixedString(text);
ui.treeView->setModel(proxyModel);
text filtering is ok, but when I clicked on QTreeView QStandardItems checkboxes (after proxy model assigned to QTreeView), I have the program crashes in slot, that connected to this QTreeView original model (before proxy was assigned).
What is the right way to processing item checkbox clicks? Need I use new connect/slot to processing model changes, or I can use the same code for original model with some changes? I just need to hide filtered items in QTreeView. In QTreeWidget is hide() method, does QTreeView has something like this, or QSortFilterProxyModel - is what I need? Thx!
UPD crashed in slot, connected to treeView:
auto item = messagesModel->itemFromIndex(index); // item is NULL because proxyModel is set for TreeView now
if(item->whatsThis().isEmpty()) return; // error below
#ifndef QT_NO_WHATSTHIS
inline QString whatsThis() const {
return qvariant_cast<QString>(data(Qt::WhatsThisRole));
}
inline void setWhatsThis(const QString &whatsThis);
#endif
because I set proxyModel to treeView, but messagesModel have whatsThis...
I changed my code with that:
QStandardItem* item;
if(ui.leFilter->text().isEmpty())
item = messagesModel->itemFromIndex(index);
else
item = messagesModel->itemFromIndex(proxyModel->mapToSource(index));
if(item->whatsThis().isEmpty()) return;
and it works. Is that correct way? Proxy model is member of my UI class ... not local.
UPD how can I update source model when checkbox checked in proxyModel?
UPD 2 I have load "original" model for QtreeView and show it. When I edit text in QListEdit, I use proxyModel (code from 1st post). When text edited, I have check checkboxes in QtreeView (now proxyModel is active) and at this step all is ok. But when I do some changes in UI, in QTreeView set the original model and it has no changes that was made for proxyModel. How can I notify and update items in source Model with new data from proxyModel?
UPD3 Yes, source model is also modified ... I have just clear it)

Exclusive checkbox in QListView

I'm trying to do exclusive checkboxes as a QListView items. I'm using QStandardItemModel as a model with QStandardItem's.
I'm adding items to the list dynamically and set it checkable:
QStandardItem *item = new QStandardItem(treeView->model()->data(index).toString());
item->setCheckable(true);
m_categoriesModel->appendRow(item);
I tried connect all items to QSignalMapper but QStandardItem doesn't have checked(bool) signal (basically it does not have any).
Is there any way to solve the problem?
You can always make it in the way described below. Firstly connect the clicked signal of ListView to the slot which will handle your items click. Secondly inside of the slot you can get the item from QModelIndex and check the state of the item. Below is pseudo code:
For example, in constructor of ListView:
connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(_handleItemClicked(QModelIndex)));
Slot of ListView:
void ListView::_handleItemClicked(QModelIndex index)
{
QStandardItem* item = _model->itemFromIndex(index);
if( item->checkState() == Qt::Checked) qDebug() << "Checked!";
}
There actually is a class for exactly doing this: QButtonGroup
It's easy to use:
QButtonGroup *group = new QButtonGroup(this);
group->setExclusive(true);//now only one will be checked at a time
//add all buttons
group->addButton(this->ui->myFirstCheckbox);
//...
... at least for manually added buttons. Of course you can use it for the model too, but it would require you to find all the checkbox elements inside the view...

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.