How to issue signal each time a row is edited in QListWidget? - c++

class genericTaskList : public QListWidget
{
Q_OBJECT
public:
QListWidgetItem *defaultText;
genericTaskList (QWidget *parentWidget)
{
setParent (parentWidget);
setFixedSize (445, 445);
defaultText = new QListWidgetItem ("Double click here to compose the task");
defaultText->setFlags (defaultText->flags () | Qt :: ItemIsEditable);
insertItem (0, defaultText);
QObject :: connect (this, SIGNAL (currentRowChanged (int)), this, SLOT (addDefaultText (int)));
}
public slots:
void addDefaultText (int rr)
{
std::cout << "\ndsklfjsdklfhsdklhfkjsdf\n";
insertItem (++rr, defaultText);
}
};
This code is supposed to issue a signal each time the row gets edited.
After I call "insertItem" in the constructor, the signal is issued.
But, that's it. It never gets issued after that - no matter how many times I edit the row.
What am I missing?

At first it seems like QListWidget::itemChanged is the way to go, but soon you run into a problem: the signal is sent for everything - inserts, removes, changing colors, checking boxes, etc! So then you end up trying to put in flags and filter everywhere by intercepting various signals to find out if editing was the actual event. It gets very messy.
There is also QAbstractItemModel::dataChanged , which would seem like a good solution. It even has a parameter "const QVector& lstRoles" so you could scan for Qt::EditRole and see if it was really edited. Alas, there's a catch - it gets called for everything just like QListWidget::itemChanged and unfortunately, for QListWidget anyway, the roles parameter is always empty when it's called (I tried it). So much for that idea...
Fortunately, there's still hope... This solution does the trick! :
http://falsinsoft.blogspot.com/2013/11/qlistwidget-and-item-edit-event.html
He uses QAbstractItemDelegate::closeEditor, but I prefer using QAbstractItemDelegate::commitData.
So make a connect like so...
connect(ui.pLstItems->itemDelegate(), &QAbstractItemDelegate::commitData, this, &MyWidget::OnLstItemsCommitData);
Then implement the slot like this...
void MyWidget::OnLstItemsCommitData(QWidget* pLineEdit)
{
QString strNewText = reinterpret_cast<QLineEdit*>(pLineEdit)->text();
int nRow = ui.pLstItems->currentRow();
// do whatever you need here....
}
Now you have a slot that gets called only when the list item's text has been edited!

currentRowChanged indicates the row selection has changed, not the content of the row. Perhaps you want to use currentTextChanged or itemChanged instead.
The reuse of the word current and changed in the QT docs is quite confusing.

Warning: A QListWidgetItem can only be added to a QListWidget once. Adding the same QListWidgetItem multiple times to a QListWidget will result in undefined behavior.
So even if it will emit the signal I think you should better to add newly created Item.
And when do you want the new row to be inserted ? -
as soon as item is double clicked or finishing edit - they differ.

Related

Troubles with undo history (QUndoStack, QUndoView and other)

I have two separate threads.
First thread for GUI, and second for application data.
Initially, I wanted to use QUndoStack and QUndoView.
But there was a problem - this view works directly with the stack:
https://code.woboq.org/qt5/qtbase/src/widgets/util/qundoview.cpp.html#_ZN10QUndoModel20setStackCurrentIndexERK11QModelIndex
In this case I got race condition.
To solve this problem I wrote custom myUndoView using QListView and QAbstractListModel.
Now all my slots using queued connections and I store a lightweight copy of the "real" undo stack in the custom view model.
This is same size and same order of the "real" undo stack elements.
A lightweight element contains only type of the undo command and text.
Now I have another problem. I'm not blame for this ))
I have a QLineEdit that emits signal on value changed when I click Enter key or lost focus.
This value in turn is sent to object (app model) with "real" undo stack. It works.
But this does not work when I interact with undo view too.
Repeat, I'm not blame for this. QUndoView has the same behavior.
Step by step:
QLineEdit in focus.
Changing value, still in focus.
Click the mouse in the undo view.
Oops.. currentIndexChanged() signal from undo view can be sent first,
or signal from QLineEdit can be sent first.
It always differs ..
If signal from QLineEdit was sent first - it works correctly.
The history of changes not lost.
I want to make enter/blur and other changes (not in history view) always invoked first. Probably I can use QTimer::singleShot() for delay of emit undo view signals . But not curentIndexChanged() because this signal emit with user interactions and when undo stack updated programmatically. We can not determine who make changes - user or application.
What I tried?
Intercept mouse clicks:
myUndoView::mousePressEvent(QMouseEvent *event)
{
event->ignore();
qDebug() << "catched!";
}
But sometimes it loses the clicks.
At the bottom of the list item (under the letters) is an area that pass a click to the item.
This may be a Qt bug, found in my environment: Debian, Mate, GTK+ Qt-style.
I think, I can place another transparent widget over list, and get coordinates of the click and use it:
http://doc.qt.io/qt-5/qabstractitemview.html#indexAt
to get the selected index.
Or I make all wrong?
Maybe there is an easier way?
How to make it right?
I would try blocking the list model signals while the line edit is focused.
Let's have an event filter like this:
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter(QObject * model) : _model(model){}
bool eventFilter(QObject *watched, QEvent *event);
private:
QObject * _model;
};
which keeps a private reference to the list model as a pointer to QObject, passed in constructor argument.
The filter implementation:
bool EventFilter::eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QEvent::FocusIn)
{
_model->blockSignals(true);
}
return false;
}
Keep a reference to an instance of the filter in the window class (Form, in my example), along with the list model instance reference:
private:
EventFilter * filter;
QAbstractListModel * model;
The filter has to be instantiated and installed in line edit, in Form constructor (don't forget to delete it in the destructor):
filter = new EventFilter(model); //the model is passed to the filter in construction
ui->lineEdit->installEventFilter(filter);
At this point, model events will be blocked when the line edit gets focus. To unlock them, use the line edit editingFinished slot:
void Form::on_lineEdit_editingFinished()
{
model->blockSignals(false);
}

QLineEdit editingFinished signal twice when changing focus?

I've found a few similar questions on this but these appear to refer to cases where a message box is used in the slot handler. In my case I am a bit stuck as I am getting the editFinished signal twice even when my slot handler is doing nothing.
For a test, I have an array of QLineEdit which use a signalMapper to connect the editingFinished() signals to a single slot. The signalMapper passes the array index so I can see where the signal came from.
eg:
testenter::testenter(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::testenter)
{
// setup the UI according to the .h file
ui->setupUi(this);
signalMapper = new QSignalMapper(this);
// init the labels and edit boxes
for (int i = 0; i < 10; i++)
{
pm_label[i] = new QLabel(ui->scrollArea);
QString text = QString("Number %1").arg(i);
pm_label[i]->setText(text);
pm_label[i]->setGeometry(10,20+i*30, 50, 20);
pm_label[i]->show();
pm_editBox[i] = new QLineEdit(ui->scrollArea);
pm_editBox[i]->setGeometry(80,20+i*30, 50, 20);
pm_editBox[i]->show();
signalMapper->setMapping(pm_editBox[i], int(i));
connect(pm_editBox[i], SIGNAL(editingFinished()), signalMapper, SLOT(map()));
}
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(data_entry(int)));
}
void testenter::data_entry(int entry)
{
//dummy
}
When run in the debugger, if I enter data into one box then either hit return or select another box with the mouse (ie change focus) , then it calls data_entry twice, the first time with index of the box that is losing focus and the 2nd time with the box which gets the focus.
So my question: Am I missing something? Is this expected behaviour or a bug?
If a bug, anyone know a way round it as I wanted to use this signal to do custom validation on data when it is entered (by either return, tab or mouse click to change focus).
First off, no this isn't expected behavior, i.e. selecting a QLineEdit should not cause it's editingFinished signal to be emitted.
There are a couple of possible things that may cause this problem:
You've accidentally connected a signal twice to a slot
The slot map() is causing the newly selected box to lose focus
In the same vain, if you're debugging and using a break point to detect when the slots are getting called you may be causing the QLineEdit to lose focus when the active application changes from your QWidget to your debugger, again causing the signal to be sent again.
If you're having problems because of a doubly connected slot, which doesn't seem to be the case because you're specifically getting a signal from two different QLineEdits, you can make sure that this isn't happening by specifying the connection type, the connect method actually has an additional optional argument at the end which allows you to change the type from a DefaultConnection to a UniqueConnection.
That being said, data validation is something that Qt has an established mechanism for, and I suggest that you use it if possible, look into extending the QValidator abstract base class Ref Doc. You then tell each of your QLineEdit's to use the same validator.
I have run into the same issue. It really does emit the signal twice, which is a known bug: https://bugreports.qt.io/browse/QTBUG-40 which however has not been addressed for a very long time.
Finally I found that the best solution in my case is to change the signal from editingFinished to returnPressed. As a side effect this behaves much more predictably from the user perspective. See also here: http://www.qtforum.org/article/33631/qlineedit-the-signal-editingfinished-is-emitted-twice.html?s=35f85b5f8ea45c828c73b2619f5750ba9c686190#post109943
The OP "found a few similar questions on this but these appear to refer to cases where a message box is used in the slot handler." Well, that is my situation also, and here is where I ended up. So, at the risk of going off topic...
In my situation, when my slot receives the editingFinished signal sent from the QLineEdit, I launch a modal QMessageBox to ask the user something. The appearance of that message box is what triggers the QLineEdit to send the second, undesirable editingFinished signal.
A post in the bug report (https://bugreports.qt.io/browse/QTBUG-40) mentioned by #V.K. offers a workaround which helped me. The following is my implementation of the workaround. I let Qt magic mojo automatically connect the QLineEdit signal to my MainWindow slot.
void MainWindow::on_textbox_editingFinished( void )
{
QLineEdit * pTextbox = qobject_cast<QLineEdit *>( QObject::sender() );
if ( !pTextbox->isModified() )
{
// Ignore undesirable signals.
return;
}
pTextbox->setModified( false );
// Do something with the text.
doSomething( pTextbox->text() );
}
void MainWindow::doSomething( QString const & text )
{
QMessageBox box( this );
box.setStandardButtons( QMessageBox::Yes | QMessageBox::No );
box.setText( "Are you sure you want to change that text value?" );
if ( box.exec() == QMessageBox::Yes )
{
// Store the text.
m_text = text;
}
}

Catch QTableWidgetItem check state change

I have one QTableWidget with some QTableWidgetsItems on it. Some items use checkboxes. I've added the checkboxes using the follow code:
QTableWidgetsItem->setCheckState(Qt::Checked);
I would like now to call some function when this checkbox state change. Using a signal for example.
What may be the easiest way to accomplish this?
The easiest way to do this is to capture signal(s) of QTableWidget with slot(s) in the class that contains QTableWidget. While it would seem that QTableWidget::itemActivated might be our best bet, it is uncertain whether or not this is emitted when the Qt::CheckState is equal to Qt::Checked. In addition, even if this was true, the signal would not provide you the capabilities of handling item unchecking which your application may need to do.
So, here is my proposed solution. Capture the QTableWidget::itemPressed and QTableWidget::itemClicked signals with slots defined in the class that contains the QTableWidget. As itemPressed should be called BEFORE the mouse button is released, and itemClicked should be called AFTER the mouse button is released, the Qt::CheckState for that QTableWidgetItem should only be set in between these two signal emissions. Thus, you can determine exactly when a QTableWidgetItem's checkState has changed with low memory overhead.
Here is an example of what these slots could look like:
void tableItemPressed(QTableWidgetItem * item)
{
// member variable used to keep track of the check state for a
// table widget item currently being pressed
m_pressedItemState = item->checkState();
}
void tableItemClicked(QTableWidgetItem * item)
{
// if check box has been clicked
if (m_pressedItemState != item->checkState())
{
// perform check logic here
}
}
And the signals/ slots would be connected as follows:
connect(m_tableWidget,SIGNAL(itemPressed(QTableWidgetItem *)),this,SLOT(tableItemPressed(QTableWidgetItem *)));
connect(m_tableWidget,SIGNAL(itemClicked(QTableWidgetItem *)),this,SLOT(tableItemClicked(QTableWidgetItem *)));
Where m_tableWidget is the QTableWidget * you associate with your table widget.

How do I properly set up generic QT actions for a menu constructed at run time?

I am populating a sytem tray icon menu (QMenu) from entries in an xml file which is read when my application starts up.
I am unsure of how to properly set up the SLOT end of the action:
QList<CMenuItem> menuItems = m_layout->getMenuItems();
QListIterator<CMenuItem> iter(menuItems);
while (iter.hasNext())
{
CMenuItem menuItem = iter.next();
QAction *action = new QAction(menuItem.qsTitle, this);
connect(action, SIGNAL(triggered()), this, SLOT(launchMenuItem()));
trayIconMenu->addAction(action);
}
How does my "launchMenuItem()" SLOT know which menu item was triggered? I can't make a SLOT for each menu item as I don't know how many items will exist until run time.
I can think of some ugly ways to do this, but I am looking for the RIGHT way.
What I usually do is to use QAction::setData(const QVariant&) to store whatever action ID I need. Then on slot side I retrieve ID with QAction::data() and behave accordingly.
Note that QVariant obviously accepts much more than basic int (which is what I use to identify actions), you can pass any QVariant-compatible info.
edit : oh! btw, this is somehow ugly because I make use of QObject::sender() to cast triggered action back. Sorry for that, but it works anyway.

qt resize window after widget remove

I'm adding widget in layout
ui->horizontalLayout->addWidget(tabwidget);
and qmainwindow resizes itself. But then I do
tabwidget->setVisible(false);
qs = sizeHint();
resize(qs);
I get the size like tabwidget was not removed from window.
I've made new button
void MainWindow::on_pushButton_2_clicked()
{
qs = sizeHint();
resize(qs);
}
and it gives correct size.
Seems I need some update function but I can't find it. Please advice
This is caused by a long-time internal Qt issue (I remember experiencing it first with Qt3). The top widget needs to receive an event to truly update its geometry, and know its boundaries. This event seems to be always received after the resize event generated by the layout, and therefore it is always too late to shrink the widget.
The solution posted in the accepted answer works. However a simpler solution posted below also works, when added after the layout :
layout()->removeWidget( widget );
QApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
resize( sizeHint() );
Basically all we need is to let the event loop run and deliver the necessary events so the top widget geometry is updated before resize() is run.
Note that this code might have side effects if you have multiple threads running, or there are events delivered to your slots. Hence it is safer not to have any code in this function after resize().
If the button slot gives you the correct result then you can always call the sizeHint() and subsequent resize() in a slot which is called by a single shot timer:
void MainWindow::fixSize()
{
QSize size = sizeHint();
resize(size);
}
void MainWindow::methodWhereIHideTheTabWidget()
{
tabwidget->setVisible(false);
QTimer::singleShot(0, this, SLOT(fixSize()));
}
This timer is set to zero delay. This means that the slot will be called immediatelly when the program returns to the main loop and hopefully after the internal widget state gets updated. If this doesn't resolve your problem you may try replacing zero with 1.