How to implement undo-redo functionality in hiding QGraphicsItem using Command-Pattern in Qt?

704 Views Asked by At

I am having a QGraphicsView which contains some QGraphicsItem I have a feature (Hide Item) which on mouse right click, hide desired QGraphicsItem(Rectangle) and its connected polylines. I have a Undo-Redo feature also.

Undo - It should cancel the effect of last command executed and show previous transformation.
Redo - It will undo the previous Undo.

To implement this Undo-Redo feature I have used Command pattern. I have implemented Undo-Redo feature for ZoomIn-ZoomOut.

Question is : I dont know how to implement Undo-Redo for Hide feature. Means what to push into stack, what to pull ?

Below Undo-Redo code is for ZoomIn-ZoomOut feature. (It is just for reference that I want to implement Hide feature something like this. )

myCommand.c

class myCommand: public QUndoCommand
{
public:
    myCommand();
    myCommand(double scale, QGraphicsScene* scene,QGraphicsView* view);    
private:
       QGraphicsItem* mItem;
       QGraphicsScene* mScene;
       QGraphicsView* mView;
       double scaleFactor;
       void undo();
       void redo();  
}

myCommand.cpp

 myCommand::myCommand(double scale, QGraphicsScene *scene,QGraphicsView* view): mScene(scene),
        mView(view),scaleFactor(scale)
    {}
void guiCommand::undo()
 {
      mView->scale(1/scaleFactor,1/scaleFactor);
 }

void myCommand::redo()
{
     mView->scale(scaleFactor,scaleFactor);
}

myView.cpp

void myView::ZoomIn()
{
    double scaleFactor = 1.1;
    view->scale(scaleFactor,scaleFactor);
    myCommand* command1 = new myCommand(scaleFactor,scene,view);
    undoStack->push(command1);
}

myView.h

public:
 QUndoStack* undoStack;    

New addition :

void myRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    vPtr = this->getPtr();
    if(vPtr->isVisible == false)
          this->hide();
    else
    {
        this->show();
        qDebug()<<"Undo Rect";
    }
}

myCommand is :

myCommand* command3 = new myCommand(isRectHiddden,vPtr,GraphName);
undoStack->push(command3);
1

There are 1 best solutions below

6
HiFile.app - best file manager On

It should be very simple given the fact that you already successfully implemented zoom-in/zoom-out.

class HideItemCommand: public QUndoCommand
{
public:
    explicit HideItemCommand(QGraphicsItem *item);    
private:
    QGraphicsItem* mItem;
    void undo();
    void redo();  
}
HideItemCommand::HideItemCommand(QGraphicsItem *item): mItem(item)
{}

void HideItemCommand::undo()
{
     mItem->show();
}

void myCommand::redo()
{
     mItem->hide();
}
void myView::hideItem(QGraphicsItem* item)
{
    item->hide();
    auto cmd = new HideItemCommand(item);
    undoStack->push(cmd);
}

So this is very simple. BUT!!! Now I am going to think one or two steps ahead. You asked only about showing/hiding undo/redo which can be implemented using the code which I suggested. But you are probably developing some drawing app so I guess that sooner or later you will want also add-item or remove-item undoable/redoable commands. And then the code which I wrote will not suffice any more. The reason is that holding item by pointer will not work any more if you remove and then add again the item using undo/redo. After redoing of remove operation, the pointer to the newly re-created object will be different the the pointer to the corresponding object which you had deleted earlier, so the item kept in HideCommand via its pointer will be invalid.

To solve this problem of invalid pointers, you need to invent some other way of recording what items you have in your scene. For example some UUID or just a sequence of integers (which is what I would do), lets call it item ID. And then keep a two-way map of these item IDs and corresponding pointers, so that you are able to translate ID to the item pointer, there and back.

Then when you create an item via some AddItemCommand you create the item and generate its ID and store the relation between the ID and the pointer to the map. You put record of this ID in the undo command. And for all other commands which will need to refer to that item (e.g. that HideCommand) you will use the ID instead of the pointer. This will allow you put all commands to the stack, use stable IDs and not volatile pointers which may change as you undo/redo adding or deleting of objects. Also RemoveItemCommand will record the ID of the removed object and if undone, the new item will be created (i.e. a new, different pointer) but with the old, known ID. So other commands referencing this ID will still be valid.

I hope I managed to explain this well. In fact it is not that difficult. You just need to understand that pointers will change over time if you add or remove items with undo/redo, but IDs can stay the same. Therefor you need to keep IDs in your commands and not pointers. This will of course change the code which I wrote a bit. But I believe you are able to adjust it from using pointers to using IDs by yourself once you implement the ID <-> pointer mapping in your app.