I have a QGraphicsView subclass that loads an image. Then, the user can draw some lines on that image by clicking and dragging.
void TabView::mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton) {
scene->addLine(line);
}
}
I added undo functionality, like this:
void TabView::mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton) {
lineList<<line;
scene->addLine(lineList.last());
}
}
void TabView::keyPressEvent(QKeyEvent * event){
int key = event->key();
switch(key){
case Qt::Key_Delete:
{
lineList.removeLast();
foreach(QLineF line, lineList){
scene->addLine(line);
}
scene->update();
break;
}
}
}
But this does not work. I have tried this
case Qt::Key_Delete:
QGraphicsLineItem *item = new QGraphicsLineItem(lineList.last());
scene->removeItem(item);
scene->update();
break;
but this also doesn't work.
My problem is: How can I undo or just delete items one by one in a QgraphicsScene?
EDIT:
look at this:
case Qt::Key_Delete:
{
itemList = scene->items();
itemList.removeLast();
foreach(QGraphicsItem *item, itemList){
scene->addItem(item);
}
scene->update();
break;
}
It expected it to work, but it isn't.
I am really wondering why it doesn't work?
In your deletion code:
case Qt::Key_Delete:
QGraphicsLineItem *item = new QGraphicsLineItem(lineList.last());
scene->removeItem(item);
scene->update();
break;
You are creating a new QGraphicsLineItem object. This object does not exist in the scene so executing scene->removeItem(item) will do nothing.
You need to keep track of all the QGraphicsLineItem objects that you add to the scene, not the QLineF objects that you use to create them with.
Have you thought about using the Qt Undo Framework?
As already suggested, Qt Undo Framework shall be best suited to you.
However, if you want to implement your own UNDO / REDO functionality, then you will have to maintain stack lists of the operations for undo and redo.
For example, you can have a stack called listUndo and a stack called listRedo.
For every addition of line or any other operation conducted by the user, push the operation on the listUndo stack. Also, remember to clear the listRedo stack on every operation by the user.
If the user presses "UNDO", then pop out the last operation done from the listUndo stack, undo that operation and push that operation on the listRedo stack.
If the user presses "REDO", then pop out the last operation undone from the listRedo stack, redo that operation and push that operation on the listUndo stack.
Ofcourse as I said, the above seems a bit tricky and bug-prone, so best would be to use a built in functionality of Qt Undo Framework.
Related
I found time to investigate a bit into QT, and it is very interesting for me. However, right now I am encountering a problem that I am not aware about how to solve it. My aim is actually simple. I have a QCheckBox that I want to activate. If it is activated, I am starting a process (I am opening a file, reading it, taking some values out and change different labels accordingly). This process is repeated until the user is deactivating the QCheckBox. Some small code example to get a better idea of what I am going to do.
void Analyzer::on_actualTemperature_stateChanged(int arg1)
{
// Read data and change labels
if (arg1 != 0)
{
qDebug() << "Start data analysis";
// Infinity loop to get the data and display it
while true
{
// Open file and extract data
const actualTemperature = getData();
// Change any label or do something with the data
ui->anyLabel->setText(actualTemperature);
// Some break
QThread::sleep(1);
// Leave the loop if user deactivate the QCheckBox
// Something like on_actualTemperature_stateChange == 0
}
}
// Stop reading the data
else
{
qDebug() << "Stop data analysis";
}
}
It is obvious that after activating the QCheckBox, the loop will not finish at all and the GUI will not recognize anything anymore. Hence, I guess I have to start some new thread and have to kill it. However, I have no idea how to proceed here. An idea would be:
void Analyzer::on_actualTemperature_stateChanged(int arg1)
{
// Read data and change labels
if (arg1 != 0)
{
// Start reading the file and updating the label using some other thread
startThread(XY);
}
// Stop reading the data
else
{
// Kill thread 1234
killThread(XY);
}
}
Any hint is warmly welcomed and I hope this question is not too basic for you. Thank you for reading, Tobi.
I think killing a running thread is not a decent behavior. Let's be gentle to our threads with a loop control variable. In this example it named keepLoop. Set keepLoop when checkbox checked. Then start thread if it is not running. We are using QtConcurrent::run, and monitoring it by a QFuture in this case.
connect(ui->checkBox, &QCheckBox::toggled,
[&](const bool checked) {
analyzer->keepLoop = checked;
if (checked && !future.isRunning())
future = QtConcurrent::run(analyzer, &Analyzer::on_actualTemperature_stateChanged);
}
);
Don't call user interface slots directly, instead connect them to signals. Connections will be queued connection when signals emitted from another thread. It means slots will be called in event loop of main thread and changes will be shown when the next frame painted.
connect(analyzer, &Analyzer::temperatureCalculated, ui->anyLabel, &QLabel::setText);
Our asynchronous function does not forced to die immediately when user toggle checkbox. Instead we letting it to finish the iteration it already on halfway through.
Analyzer::on_actualTemperature_stateChanged() {
while (keepLoop) {
// Open file and extract data
const QString& actualTemperature = getData();
// send data
emit temperatureCalculated(actualTemperature);
}
}
You can use atomic bool if you want a more precise loop control.
Bonus:
If you don't want to mess with threads, you can avoid GUI freezing by using QTimer to run your loop periodically in main thread.
given this code:
void FooBar::ProcessExitHandler(QProcess* someProcess, QString logsPath)
{
if (clientProcess->exitCode() != 0)
{
QMessageBox* dialog = new QMessageBox();
dialog->setText("bye bye");
dialog->setStandardButtons(0);
QObject::connect(dialog, &QMessageBox::finished, [this](int) {
if (mMainWindow->AutoCloseCheckBoxChecked())
{
delete dialog; //TODO: need to confirm what is the correct way
this->quit();
}
});
dialog->show();
dialog->activateWindow();
}
else
{
if (mMainWindow->AutoCloseCheckBoxChecked())
{
delete dialog; //TODO: need to confirm what is the correct way
this->quit();
}
}
}
Is calling delete dialog like that correct? Is there a more QT idiomatic way of doing this?
Also, something that has caused me confusion is the idea (from the docs) that I should be passing a parent to the constructor of the message box. Then I would get automatic memory management, right? Is that the QT style I should shoot for?
I'm aware that since the app is exiting anyway, the leak "doesn't matter", but I want to do the right thing.
The right way is to use setAttribute
QMessageBox* dialog = new QMessageBox();
dialog->setAttribute(Qt::WA_DeleteOnClose);
By setting the attribute WA_DeleteOnClose, the destructor will be called at the right moment.
When you manually call delete, the pointer will keep its value (the address) although it isn't valid anymore. If for some reason you were to reuse that pointer again, the app would crash.
\warning Deleting a QObject while pending events are waiting to be
delivered can cause a crash. You must not delete the QObject directly
if it exists in a different thread than the one currently executing.
Use deleteLater() instead, which will cause the event loop to delete
the object after all pending events have been delivered to it.
https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobject.cpp.html#881
Although I'm using Qt from Python via PyQt, this question is equally applicable to pure Qt, just the syntax is a bit different, the issue is the same:
When we want to dispose of a QGraphicsItem object in our scene, we call scene.removeItem(item). When we want to dispose of a QGraphicsObject object in our scene, we call scene.removeItem(item) because it derives from QGraphicsItem, but we ALSO call item.deleteLater() because it derives from QObject and that is the recommended way of disposing of QObjects (so that pending signals to and from the item are properly handled).
PROBLEM is that slots in the object item may can get called AFTER the item has been removed from the scene, due to how deleteLater() functions. This requires that we test for self.scene() being None in slots. But this is error prone as it is easy to forget to do that, and forgetting this leads to exception if slot is called.
Another approach is to not call deleteLater() before removing the item from the scene, but this requires manually disconnecting the item from other objects. This has similar disadvantage to testing for self.scene() being None in slots, and its easy to forgot to disconnect a slot.
A better way of mitigating this source of error (if there are no hidden gotchas) would be to NOT call scene.removeItem(item) when item is a QGraphicsObject, and JUST call its deleteLater(): it seems, based on some simple tests, that the scene automatically removes item from its list when it eventually gets destroyed. HOWEVER, I can't find any Qt documentation that states this, and I might have just been lucky; perhaps in a more realistic scenario I would get a memory leak or a crash.
So I'm leaning towards calling deleteLater() without calling removeItem() when item is a QGraphicsObject, do you think this is safe?
Below is the source code for the QGraphicsItem destructor (taken from qt-5.7/qtbase/src/widgets/graphicsview/qgraphicsitem.cpp). As you can see, it does a whole load of cleanup, as well as calling the scene's internal removeItemHelper function (which is also called by removeItem). Thus, it seems well designed to handle removal via deletion.
QGraphicsItem::~QGraphicsItem()
{
if (d_ptr->isObject) {
QGraphicsObject *o = static_cast<QGraphicsObject *>(this);
QObjectPrivate *p = QObjectPrivate::get(o);
p->wasDeleted = true;
if (p->declarativeData) {
if (static_cast<QAbstractDeclarativeDataImpl*>(p->declarativeData)->ownedByQml1) {
if (QAbstractDeclarativeData::destroyed_qml1)
QAbstractDeclarativeData::destroyed_qml1(p->declarativeData, o);
} else {
if (QAbstractDeclarativeData::destroyed)
QAbstractDeclarativeData::destroyed(p->declarativeData, o);
}
p->declarativeData = 0;
}
}
d_ptr->inDestructor = 1;
d_ptr->removeExtraItemCache();
#ifndef QT_NO_GESTURES
if (d_ptr->isObject && !d_ptr->gestureContext.isEmpty()) {
QGraphicsObject *o = static_cast<QGraphicsObject *>(this);
if (QGestureManager *manager = QGestureManager::instance()) {
const auto types = d_ptr->gestureContext.keys(); // FIXME: iterate over the map directly?
for (Qt::GestureType type : types)
manager->cleanupCachedGestures(o, type);
}
}
#endif
clearFocus();
setFocusProxy(0);
// Update focus scope item ptr.
QGraphicsItem *p = d_ptr->parent;
while (p) {
if (p->flags() & ItemIsFocusScope) {
if (p->d_ptr->focusScopeItem == this)
p->d_ptr->focusScopeItem = 0;
break;
}
p = p->d_ptr->parent;
}
if (!d_ptr->children.isEmpty()) {
while (!d_ptr->children.isEmpty())
delete d_ptr->children.first();
Q_ASSERT(d_ptr->children.isEmpty());
}
if (d_ptr->scene) {
d_ptr->scene->d_func()->removeItemHelper(this);
} else {
d_ptr->resetFocusProxy();
setParentItem(0);
}
#ifndef QT_NO_GRAPHICSEFFECT
delete d_ptr->graphicsEffect;
#endif //QT_NO_GRAPHICSEFFECT
if (d_ptr->transformData) {
for(int i = 0; i < d_ptr->transformData->graphicsTransforms.size(); ++i) {
QGraphicsTransform *t = d_ptr->transformData->graphicsTransforms.at(i);
static_cast<QGraphicsTransformPrivate *>(t->d_ptr.data())->item = 0;
delete t;
}
}
delete d_ptr->transformData;
if (QGraphicsItemCustomDataStore *dataStore = qt_dataStore())
dataStore->data.remove(this);
}
Another approach is to not call deleteLater() before removing the item from the scene, but this requires manually disconnecting the item from other objects. This has similar disadvantage to testing for self.scene() being None in slots, and its easy to forgot to disconnect a slot.
First of all, there's no reason to manually remove an item from the scene if your goal is to destroy the item. The scene tracks the item lifetime. So all you need to do is to destroy the item by appropriate means.
If none of the item's methods are on the call stack, simply delete item.
If the item's methods may be on the call stack, use the QObject::deleteLater method.
Qt's classes are mostly well designed and thus follow the Liskov Substitution Principle. The QGraphicsObject is-substitutable-for-a QObject and you can treat it as if it was, indeed, a QObject, without worrying that it happens to be something a QGraphicsItem too.
That's all there's to it. It will solve all your problems in one go.
You almost never have to call scene.removeItem directly: manage the lifetime of the items, and the scene will follow it for you. It's just like the interaction between QWidget and QLayout: widgets that are managed by layouts are still destructible and the layout will forget about the widget when the widget gets destroyed.
What I want to do is display a set of pages setting up a test. Once all the details are correct the user presses Commit and the next wizard page is displayed that I want to immediately run a series of tests in. Displaying those to the user and once complete the user can then click Next.
I know to disable Next is simply a case of returning false on isComplete() and that is implemented okay. So, I want to use the function that is called just after the widget is displayed and so I used showEvent() which was indicated to me as the function to use.
At the moment my test is just displaying a progress bar as a test hence using a timer.
void RunTestWizardPage::showEvent(QShowEvent *event)
{
ui->statusEdit->setText("Running Tests");
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(100);
}
void RunTestWizardPage::update()
{
static int i = 10;
ui->statusEdit->append("Running Tests...");
ui->testProgress->setValue(i++);
if(i == 100)
{
i = 0;
timer->stop();
complete = true;
emit completeChanged();
}
}
However this function appears to be called twice (and I think before the widget display although that may be a trick of my debugging) and as such it causes issues with the timer I think as the timer never ends. I did read in the docs about spontaneous events but from what I can see both calls to the function are not spontaneous.
Is it being called twice intentional and if so how do I stop it or is there another function to use?
Thanks!
There is QWizardPage::initializePage() which is called just before showing the page.
When user types in a QWidget based window, I wanted a QLineEdit to process
all input keys,
so I tried the following two solution in keyPressEvent() of that QWidget:
A.
void Window::keyPressEvent (QKeyEvent *e)
{
switch (e->key())
{
// handle other short cuts
default:
QApplication::sendEvent (lineEdit , e);
break;
}
}
Well, this sometimes crashes the whole interface, especially when I resize window.
B.
void Window::keyPressEvent (QKeyEvent *e)
{
switch (e->key())
{
// handle other short cuts
default:
if ( ! lineEdit.hasFocus () )
{
lineEdit.setFocus ();
lineEdit.setText (e->key());
// i wanted to push the first key input to that QLineEdit , but how ?
// or i'll miss it
}
break;
}
}
Also I'm thinking about giving lineEdit focus all the time, but I can't do that as other events needed to be handled by the main UI.
Update
It won't crash when I filter key inputs, but why ?
default:
if ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ||
(e->key() >= Qt::Key_A && e->key() <= Qt::Key_Z )
)
QApplication::sendEvent(filter , e);
break;
}
I believe you are running into a crash because you are using sendEvent to send an event object that you don't have control over.
I don't think the Qt event system expects you to grab its events and throw them in other directions, and it's likely that the event object is getting destroyed before the line edit expects. In the case where you're filtering out input keys, it's probably not crashing because the line edit doesn't care about those kinds of key strokes and isn't using the event object as much as it would otherwise.
If you really want to use the sendEvent() functionality, then I would suggest you create your own QKeyEvent on the stack and pass it to the sendEvent() function (as demonstrated here), or you can just do something like this:
lineEdit.setText( lineEdit.text() + event->text() );
When a widget does not handle an event, it forwards it to its parent. So using sendEvent() to forward to a child is dangerous, as it can make a recursion.
The easiest way of doing it would be to use QKeyEvent::text instead of QKeyEvent::key and you should be OK. You might also try to create a copy of QKeyEvent and pass it to your QLineEdit. Thos are rather hacks than solutions though. If you need shortcuts in main window while QLineEdit has focus (assuming it is in this window) you can use QShortcut with Qt::WidgetWithChildrenShortcut context - this way you can keep your LineEdit active at all times.