2D drawing of qt: adding an undo stack menu

Posted by wiggst3r on Fri, 03 Jan 2020 23:25:40 +0100

Summary: add a QUndoStack and QUndoView to mainwindows. Add different actions to edit [undo action & redoaction] and view[Undo stack] in the constructor.

Note: the undo and redo operations in edit only need to add the response API, and the qt editor will implement them automatically. But the action in view needs to be associated with a slot function implementation: if there is no undo view, create a

Pass QUndoStack as an argument to scene's constructor, and point to QUndoStack with a pointer.

1,mainwindows

1.1. In mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

/*https://blog.csdn.net/elf001/article/details/8980083*/
class Scene;
class QUndoStack;
class QUndoView;

#include <QMainWindow>

#include "scene.h"


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();
private:
    Scene* m_scene;
    QUndoStack*  m_undoStack;           // Revocation stack
    QUndoView*   m_undoView;            // Undo stack view

public slots:   //Definition of adding slot method
  void showMessage( QString );        // Show messages on the status bar
  void showUndoStack();               // Open a new undo stack form
};

#endif // MAINWINDOW_H

1.2. In mianwindow.cpp

#include "mainwindow.h"
#include <QMenuBar>
#include <QStatusBar>
#include <QGraphicsView>

#include <QUndoStack>

#include <QUndoView>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    //Add menu to menu bar: add drop-down menu
    menuBar()->addMenu( "&File" );
    QMenu* editMenu = menuBar()->addMenu( "&Edit" );  //Store the menu item pointer of the edited view in the local variable
    QMenu* viewMenu = menuBar()->addMenu( "&View" );   //Because it will be used later
    menuBar()->addMenu( "&Simulate" );
    menuBar()->addMenu( "&Help" );

    //New undo stack and contact menu operation
    m_undoStack = new QUndoStack(this);
    m_undoView = 0;

    viewMenu->addAction("Undo stack", this, SLOT(showUndoStack()));

    //https://blog.csdn.net/ljhandlwt/article/details/52354139
    QAction *undoAction = m_undoStack->createUndoAction(this);
    QAction *redoAction = m_undoStack->createRedoAction(this);
    undoAction->setShortcut( QKeySequence::Undo ); //Ctrl+Z, Alt+Backspace:setShortcuts() function for shortcut keys
    redoAction->setShortcut( QKeySequence::Redo ); //QKeySequence of Ctrl+Y, Shift+Ctrl+Z:Qt defines many built-in shortcut keys for us
    editMenu->addAction(undoAction);
    editMenu->addAction(redoAction);  //Add two actions to edit
    statusBar()->showMessage("QSimulate has started");

    // Create a central view part of the scene and display it
    m_scene               = new Scene(m_undoStack);  //View is the view, responsible for displaying; Scene is the document, responsible for storing data. So from this point of view, we can think that a Scene can be associated with multiple views, just like a data can have multiple views to view it.
    QGraphicsView*   view = new QGraphicsView( m_scene ); //Qgraphicscene is a view. It cannot exist alone. It must be associated with at least one QGraphicsView
    view->setAlignment( Qt::AlignLeft | Qt::AlignTop );
    view->setFrameStyle( 0 );
    setCentralWidget( view );

    //Associate signal with slot: when m_scene transmits message signal, MainWindow receives signal and executes showMessage slot function
    connect( m_scene, SIGNAL(message(QString)), this, SLOT(showMessage(QString)) );
}

MainWindow::~MainWindow()
{

}

void  MainWindow::showMessage( QString msg )
{
  statusBar()->showMessage( msg );  // Display messages on the main window status bar
}

void  MainWindow::showUndoStack()
{
    if(m_undoView == 0)  //Determine whether the undo stack view is created
    {
        m_undoView = new QUndoView(m_undoStack); //Create and set the form title
        m_undoView->setWindowTitle("QSimulate - Undo stack");
        m_undoView->setAttribute( Qt::WA_QuitOnClose, false );  //Undo stack view is closed by the user and the application will not exit
    }
    m_undoView->show();
}

2,Scene

2.1,Scene.h

#ifndef SCENE_H
#define SCENE_H

class QGraphicsSceneMouseEvent;
class QUndoStack;  //Add a pre definition of the QUndoStack class.

#include <QGraphicsScene>
#include "sation.h"

class Scene : public QGraphicsScene
{
  Q_OBJECT
public:
  Scene( QUndoStack* ); // Constructor takes a QUndoStack pointer parameter.
signals:
  void  message( QString );                                  // Text message signal
protected:
  void  mousePressEvent( QGraphicsSceneMouseEvent* );        // Receive mouse down events
  void  contextMenuEvent( QGraphicsSceneContextMenuEvent* ); // Receive context menu events

private:
  QUndoStack*  m_undoStack;                 // Undo stack store pointer
};

#endif // SCENE_H

2.2,Scene.cpp


#include "scene.h"

#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
#include <QAction>
#include <QUndoStack>
Scene::Scene( QUndoStack* undoStack  ) : QGraphicsScene()
{
    addLine( 0, 0, 0, 1, QPen(Qt::transparent, 1) );
    m_undoStack = undoStack;
}


void  Scene::mousePressEvent( QGraphicsSceneMouseEvent* event )
{
    // Set local variables and check whether the selected station exists
    qreal x = event->scenePos().x();
    qreal y = event->scenePos().y();
    QTransform transform;
    Sation *station = dynamic_cast<Sation *>(this->itemAt(QPointF(x, y), transform)); //pointf returns a base class, now let it point to a derived class

    // If the station is not selected and the left mouse button is pressed, create a new station
    if ( station == 0 && event->button() == Qt::LeftButton )
    {
        addItem( new Sation( x, y ) );
        emit message( QString("Station add at %1,%2").arg(x).arg(y) );
    }
    // Call mousePressEvent of the base class to handle other mouse down events
    QGraphicsScene::mousePressEvent( event );
}
/*
 * Add the code for the new method contextMenuEvent. When the user presses the right mouse button in the scene, this method will be called. We only want a context menu to be displayed when the user points to a station.
 * Then we create a menu with only the delete radio option.
 * If the user selects this option, the station will be removed from the scene, removed from memory, and a message will be sent.
*/
void  Scene::contextMenuEvent( QGraphicsSceneContextMenuEvent* event )
{
    // We only want to show the menu when the user selects a station
    qreal     x       = event->scenePos().x();
    qreal     y       = event->scenePos().y();

    QTransform transform;
    Sation *station = dynamic_cast<Sation *>(this->itemAt(QPointF(x, y), transform)); //pointf returns a base class, now let it point to a derived class
    if(0 == station) return;
    // Display context and corresponding actions
    QMenu     menu;
    QAction*  deleteAction = menu.addAction("Delete Station");
    if ( menu.exec( event->screenPos() ) == deleteAction )
    {
        removeItem( station );
        delete station;
        emit message( QString("Station deleted at %1,%2").arg(x).arg(y) );
    }
}

3. The rest remains the same: the undo here is associated with secne, which has nothing to do with item

3.1,sation.h

#ifndef SATION_H
#define SATION_H

#include <QGraphicsItem>

/*This class can be used in scene or mainwindow*/
class Sation : public QGraphicsItem
{
public:
    Sation(qreal, qreal);

    void paint(QPainter*,   // //paint virtual functions: drawing icons
               const QStyleOptionGraphicsItem*,
               QWidget*
            );
    QRectF boundingRect() const{  // boundingRect virtual function: define the external border corresponding to each icon
        return QRectF(-6.5, -13, 13, 18);//Returns a rectangle: slightly larger than the icon we want to draw
    }


};

#endif //
SATION_H

3.2,sation.cpp

#include "sation.h"

#include <QPainter>

Sation::Sation(qreal x, qreal y):QGraphicsItem()
{
    setPos(x, y);
    setFlags(QGraphicsItem::ItemIsMovable |
             QGraphicsItem::ItemIsSelectable |
             QGraphicsItem::ItemIgnoresTransformations
             );
}
//In the constructor, we set the coordinates according to the parameters passed by x and y. Draw our radio station icon in the paint function.
void  Sation::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    //Draw the station icon, which must be smaller than the rectangle of the border
    painter->setRenderHint( QPainter::Antialiasing );
    painter->setPen( QPen( Qt::black, 2 ) );
    painter->drawRect( -4,  -3,  8,   7 );   //Draw a rectangle: use the position of the mouse click as the starting center
    painter->drawLine(  0,  -4,  0, -11 );
    painter->drawLine( -5, -11,  0,  -6 );
    painter->drawLine( +5, -11,  0,  -6 );
}

 

4,main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

Topics: Qt REST