1

I have a QGraphicsView, which contains rectangle, polyline, text etc. I also have some features to transform the view like zoom in, zoom out, Fit in view, change the color on mouse right click etc. I want to add few more features like Undo and Redo.

Undo means, user should cancel the effect of last command executed and show previous transformation. So If user did transformation like zoom in -> zoom in -> fit in -> zoom in and now pressed Undo then view's fit in position should get displayed.

I have tried Command pattern to implement Undo-Redo feature. But I did it to add/remove rectangle,polyline in design. And it worked.
HCommand.h

#include <QUndoCommand>
#include<QGraphicsItem>
#include<QGraphicsScene>

class myCommand : public QUndoCommand
{

public:
        HCommand(QGraphicsItem* mItem, QGraphicsScene* scene);
        HCommand(QGraphicsScene* scene, QGraphicsView* mView);// for fitIn

 private:
        QGraphicsItem* mItem;
        QGraphicsScene* mScene;
        QGraphicsView* mView;

        void undo();
        void redo();
};          
    

HCommand.cpp

#include "hcommand.h"

HCommand::HCommand(QGraphicsItem* item, QGraphicsScene* scene):
   mItem(item), mScene(scene)

{}

void HCommand::undo()
{
    if(mItem)
        mScene->removeItem(mItem);
}

void HCommand::redo()
{
    if(mItem)
        mScene->addItem(mItem);
}
              

Widget.h

class Widget : public QGraphicsView
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    Rectangle r;
    PolyLine p;
    QUndoStack* undoStack;
    void undo();
    void redo();
private:
    Ui::Widget *ui;
    QGraphicsScene* scene;
    QGraphicsView* view;
}     

Widget.cpp

Widget::Widget(QWidget *parent)
    : QGraphicsView(parent)
    , ui(new Ui::Widget)
{     
    ui->setupUi(this);
    scene = new QGraphicsScene(this);
    view = new QGraphicsView(this);    
    view->setScene(scene);
    undoStack = new QUndoStack(this);
    ui->verticalLayout_2->addWidget(view);       
}          
void Widget::undo()
{
    undoStack->undo();
}

void Widget::redo()
{
    undoStack->redo();
}       
void Widget::on_schematicBtn_clicked()
{
    QGraphicsRectItem* in = r.createRect(20,160,20,20);
    scene->addItem(in);
    HCommand* com = new HCommand(in,scene);
    undoStack->push(com);        
    // many rectangles and polyline code, same as above     
}       
   

But now I want to do Undo-Redo for Zoom In , Zoom Out, Fit In. But whenever I zoom in/out, I zoomed in/out full view

void Widget::on_zoomIn_clicked()
{
    double sFactor = 1.2;
    view->scale(sFactor,sFactor);
}


void Widget::on_zoomOut_clicked()
{
    double sFactor = 1.2;
    view->scale(1/sFactor,1/sFactor);
}         
 
void Widget::on_fitInBtn_clicked()
{
    view->resetTransform();
}

So the question is :

In above code I am pushing particular rectangle in stack so for zoom in/out how to push full view in stack ? So that, later I can pull it out from stack ? Or this can be implemented differently ?

Any help is appreciated.

tushar
  • 313
  • 4
  • 10
  • First you do not need to add the item before pushing the command. The redo function will be called on push. And I do not unterstand your question, you can easily write a class which has an undo to zoom in and a redo to zoom out. – gerum Mar 10 '22 at 17:09
  • @gerum : Sorry but I am not understanding what to push in the stack while undo and redo ? I am zooming in whole view not any single item. Can you help me in understanding ? – tushar Mar 10 '22 at 17:24
  • As a side note: operations like "zoom in" and "zoom out" don't typically change the state of the data that the user is interacting with; rather they only change the state of the viewport that the user is using to view the data. Given that, are you sure you *want* to put those operations onto the undo/redo stack? I would find it surprising that an "undo" operation un-did a zoom, just as I would find it surprising if an "undo" operation un-did my scrolling of a scroll-bar. – Jeremy Friesner Mar 10 '22 at 19:22
  • @tushar Ask yourself what do you push to the stack to add an item, an custom class implementing thr QUndoCommand interface. So if you want to redo/undo a zoom,you should write a new class where the implementation of the redo function zoom in or out. I want to add that this answer is pure technical and I would also not recommend to use the unfo stack for something like zooming. – gerum Mar 10 '22 at 22:14
  • @JeremyFriesner Yes, I am sure. I want to put those operations on stack. Even I want to store history what user did with design so it will be useful for debugging purpose. I am not sure, how to maintain history may be last 10 activities user has performed. (currently we only have zoom in/out/fit in feature. Later on we are going to add more feature ) But I want it. So can you tell me how to do it ? – tushar Mar 11 '22 at 00:15
  • 1
    @tushar I imagine you'd need to create a subclass of `QUndoCommand` and override the `undo()` and `redo()` methods to call `view->scale(x)` with the appropriate value of `x`.... and push that object onto the undo-stack whenever the user clicks a zoom-in or zoom-out button. – Jeremy Friesner Mar 11 '22 at 00:20
  • @JeremyFriesner Your suggestion worked for Zoom in/out. But I could not use it for Fit In. I have updated Fit in code in question. And I tried this way. `void Widget::on_fitInBtn_clicked() { view->resetTransform(); ICommand* command3 = new ICommand(scene,view); undoStack->push(command3); }` – tushar Mar 12 '22 at 07:56
  • @JeremyFriesner I know above way is wrong but how to correct it ? Can you help me ? – tushar Mar 12 '22 at 07:58
  • @tushar since your fit-in action overwrites the view’s transform with a default one, executing it destroys the information of what the transform was before the fit-in was executed. That means that in order to undo it later you’d need to store the view’s current transform as a member-variable in your QUndoCommand object, before resetting the view. Then you can undo() it by restoring the saved transform. – Jeremy Friesner Mar 12 '22 at 14:34
  • @JeremyFriesner Sorry but this time I did not get you properly. I tried this way : `void Widget::on_fitInBtn_clicked() { QGraphicsView* mView = view; ICommand* command3 = new ICommand(scene,mView); undoStack->push(command3); view->resetTransform(); }` – tushar Mar 13 '22 at 18:40
  • @JeremyFriesner I am getting blank screen after pressing `Undo` button. Can you correct me ? I have updated `ICommand` constructor for `fitIn`. – tushar Mar 13 '22 at 18:45
  • @tushar I won't debug your program for you, but if you'd like some guidance on how to debug it for yourself, I find this article to be helpful: https://ericlippert.com/2014/03/05/how-to-debug-small-programs/ – Jeremy Friesner Mar 13 '22 at 18:47
  • @JeremyFriesner Sorry but I did not want to say debug for me. I just want to confirm, whether the changes I have done are correct or not ? Is my approach in `on_fitInBtn_clicked()` is correct ? ( in my previous comment, I have posted a code ) – tushar Mar 14 '22 at 05:42
  • @tushar I dont see anything in the code in the comment that would save the view's current transform into `command3`; how will your `undo()` method know what to restore the transform to if you never saved it? – Jeremy Friesner Mar 14 '22 at 14:57

0 Answers0