0

I have QGraphicsView which contains some QGraphicsItem. This view has some feature like zoom-in, zoom-out, fitIn, Undo-Redo.
My fitIn feature is not working in Undo-Redo functionality.
( To implement Undo-Redo I have used Command-Pattern in Qt. )

myCommand.h

class myCommand: public QUndoCommand
{
public:
    myCommand();
    myCommand(QTransform t, int hr, int vr, QGraphicsView* v);
    void undo();
    void redo();
    QTransform t;
    int hScrollBar;
    int  vScrollBar;
    QGraphicsView* mView;
};

myCommand.cpp

myCommand::myCommand(QTransform t, int hr, int vr, QGraphicsView *v)
{
    this->t = t;
    this->hScrollBar= hr;
    this->vScrollBar= vr;
    this->mView = v;
}

void myCommand::undo()
{
    mView->setTransform(t);
    mView->horizontalScrollBar()->setValue(hScrollBar);
    mView->verticalScrollBar()->setValue(vScrollBar);
}

void myCommand::redo()
{
   myView mv;
   mv.FitInView();
}

myView.cpp

 void myView::FitIn()
    {
        FitInView();
        QTransform t = view->transform();
        int hrValue = view->horizontalScrollBar()->value();
        int vrValue = view->verticalScrollBar()->value();
        myCommand* Command1 = new myCommand(t,hrValue,vrValue,view);
        undoStack->push(Command1);   
    }

void myView::DrawDesign()
{
   // Logic for inserting all the Rectangles and polylines.
   QTimer::singleShot(100, this, [&]() {
        FitInView();
    });
}

void myView::FitInView()
{
    QRectF bounds = scene->sceneRect();
    QRectF rect  {0,0,200,200};
    if (bounds.width() < 200)
    {
        rect .setWidth(bounds.width());
        bounds.setWidth(200);
    }
    if (bounds.height() < 200)
    {
        rect.setWidth(bounds.height());
        bounds.setHeight(200);
    }
    view->fitInView(bounds, Qt::KeepAspectRatio);
    view->updateGeometry();
}

myView.h

public:
      QUndoStack* undoStack;

FitInView fits my design perfectly but it does not work in Undo-Redo feature.
I think I am making a mistake in myCommand::undo() and myCommand::redo() function.

tushar
  • 313
  • 4
  • 10
  • Note for future readers: this is a follow up question of [How to get current visible portion of QGraphicsView in Qt?](https://stackoverflow.com/questions/71636589/how-to-get-current-visible-portion-of-qgraphicsview-in-qt) – m7913d May 08 '22 at 13:08

1 Answers1

1

Based on the many question you posted on the Qt's Undo Framework, it seems to me you are missing an essential part of the Command pattern:

The Command pattern is based on the idea that all editing in an application is done by creating instances of command objects. Command objects apply changes to the document and are stored on a command stack. Furthermore, each command knows how to undo its changes to bring the document back to its previous state. [1]

So all changes, should be implemented inside the QUndoCommand (follow the link to discover a basic usage example), i.e. inside QUndoCommand::redo. Once the command is pushed on the QUndoStack, using QUndoStack::push, the change (i.e. QCommand::redo) is automatically performed:

Pushes cmd on the stack or merges it with the most recently executed command. In either case, executes cmd by calling its redo() function. [4]

Steps to create a new QUndoCommand

  1. Implement the desired changes inside QUndoCommand::redo (and not somewhere else).
  2. Write the inverse of this command inside QUndoCommand::undo. If needed, capture the initial state inside the QUndoCommand constructor, to easily revert QUndoCommand::redo.

Applied to your example

So, the only action that myView::FitIn should perform, is pushing the command on the undo stack:

void myView::FitIn()
{
    undoStack->push(new fitCommand(view));   
}

Implement your changes inside the redo command:

void fitCommand::redo()
{
    mView->FitInView(); // Can be implemented using QGraphicsView::fitInView
}

As the inverse operation of fitting is not uniquely defined, we store the initial state inside the QUndoCommand constructor (to be able to restore the initial state inside QUndoCommand::undo):

fitCommand::fitCommand(myView *view)
{
    this->t = view->transform();
    this->hScrollBar= view->horizontalScrollBar()->value();
    this->vScrollBar= view->verticalScrollBar()->value();
    this->mView = view;
}

Now we can implement the undo command to revert the redo command:

void fitCommand::undo()
{
    mView->setTransform(t);
    mView->horizontalScrollBar()->setValue(hScrollBar);
    mView->verticalScrollBar()->setValue(vScrollBar);
}

Usefull reading material:

m7913d
  • 10,244
  • 7
  • 28
  • 56
  • Thank you for your EXCELLENT reply. The best reply I ever got. – tushar May 08 '22 at 16:02
  • I have some problem in `redo()`. It is not working. If I dont use `fitIn` functionality then `undo-redo` works perfectly. But If I use `fitIn` then `redo` does not work. Means if I do like `zoom-in (1)`, `zoom-in (2)`,`zoom-in (3)`, `fitIn(4)` and then if I press 2 times `Undo` then I reach at (2) and then If I press `redo` then I reach at (3) but If I press it again then I do not reach at (4). – tushar May 08 '22 at 16:06
  • I have added little bit code of `FitInView()` to understand it properly. And I have changed `undo()`and `redo()` as per your suggestion. – tushar May 08 '22 at 16:08
  • In `myCommand::redo`, you are now creating a new temporary view. This temporary view is correctly fitted into view (but never shown on screen). You should alter your original view, i.e. `this->mView`. Either you move to code of `myView::FitInView` to `fitCommand::redo` or you hold a pointer to myView (instead of QGraphicsView) in `fitCommand`. – m7913d May 08 '22 at 17:42
  • You are still calling `FitInView();` directly from `myView::FitIn`. This should only be called from inside `fitCommand::redo` as explained in this post. However, note that it isn't a good idea to change the question (drastically) after an answered to keep the question and answer useful for other users who encounter the same issue in the future. Adding extra information information is off course no problem, but try to keep the original question intact. – m7913d May 08 '22 at 17:46
  • Perfectly worked ! Now my `fitIn()` is working perfectly. Thank you for your help. And very nice reply ! :) – tushar May 09 '22 at 09:13
  • Can you look into this question ? https://stackoverflow.com/questions/72122800/can-we-use-multiple-stack-in-qts-undo-framework-command-pattern I got reply and my issue was solved. But I want you to look into `zoom-in/out` code. The problem is : `zoom-in` and `zoom-out` feature working properly but when I use `Undo` that time problem occurs. Lets say for each `zoom-in` I have incresed ratio by 2x then for 3 time zoom in size gets increses by 6x but while Undoing it, it comes to 3x. Means for each `Undo` it reduces only for 1x. Am I wrong in `scaleFactor` ? – tushar May 09 '22 at 09:18
  • For normal `zoom-in` and `zoom-out` it works perfectly. ( means `zoom-out` brings my design to original scale. ) But lets say `zoom-in (2x)`,`zoom-in (2x)`,`zoom-in (2x)` then if I used `Undo(1x)`, `Undo(1x)` , `Undo(1x)` . `Undo` does not bring my design to original scale. – tushar May 09 '22 at 09:24
  • Problem is the same as in this question: all changes should be made inside the `QUndoCommand::redo` (and `QUndoCommand::undo`), as `redo` is automatically called when pushed to the stack. So the scale factor is initially applied twice: once inside `myView::ZoomIn` and once inside `myCommand::redo`. – m7913d May 09 '22 at 10:15
  • It may be useful to debug your code (place a breakpoint inside `ZoomIn` and `redo`), so you will see that the zoom factor is indeed applied twice. – m7913d May 09 '22 at 10:21
  • It worked perfectly. Thank you once again for your explanation. – tushar May 09 '22 at 11:31