Undo Framework▲
Sélectionnez
/**
**************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the demonstration applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
***************************************************************************
*/
#include <QUndoGroup>
#include <QUndoStack>
#include <QFileDialog>
#include <QMessageBox>
#include <QRandomGenerator>
#include <QTextStream>
#include <QToolButton>
#include
"document.h"
#include
"mainwindow.h"
#include
"commands.h"
MainWindow::
MainWindow(QWidget *
parent)
:
QMainWindow(parent)
{
setupUi(this
);
QWidget *
w =
documentTabs-&
gt;widget(0
);
documentTabs-&
gt;removeTab(0
);
delete
w;
connect(actionOpen, SIGNAL(triggered()), this
, SLOT(openDocument()));
connect(actionClose, SIGNAL(triggered()), this
, SLOT(closeDocument()));
connect(actionNew, SIGNAL(triggered()), this
, SLOT(newDocument()));
connect(actionSave, SIGNAL(triggered()), this
, SLOT(saveDocument()));
connect(actionExit, SIGNAL(triggered()), this
, SLOT(close()));
connect(actionRed, SIGNAL(triggered()), this
, SLOT(setShapeColor()));
connect(actionGreen, SIGNAL(triggered()), this
, SLOT(setShapeColor()));
connect(actionBlue, SIGNAL(triggered()), this
, SLOT(setShapeColor()));
connect(actionAddCircle, SIGNAL(triggered()), this
, SLOT(addShape()));
connect(actionAddRectangle, SIGNAL(triggered()), this
, SLOT(addShape()));
connect(actionAddTriangle, SIGNAL(triggered()), this
, SLOT(addShape()));
connect(actionRemoveShape, SIGNAL(triggered()), this
, SLOT(removeShape()));
connect(actionAddRobot, SIGNAL(triggered()), this
, SLOT(addRobot()));
connect(actionAddSnowman, SIGNAL(triggered()), this
, SLOT(addSnowman()));
connect(actionAbout, SIGNAL(triggered()), this
, SLOT(about()));
connect(actionAboutQt, SIGNAL(triggered()), this
, SLOT(aboutQt()));
connect(undoLimit, SIGNAL(valueChanged(int
)), this
, SLOT(updateActions()));
connect(documentTabs, SIGNAL(currentChanged(int
)), this
, SLOT(updateActions()));
actionOpen-&
gt;setShortcut(QString("Ctrl+O"
));
actionClose-&
gt;setShortcut(QString("Ctrl+W"
));
actionNew-&
gt;setShortcut(QString("Ctrl+N"
));
actionSave-&
gt;setShortcut(QString("Ctrl+S"
));
actionExit-&
gt;setShortcut(QString("Ctrl+Q"
));
actionRemoveShape-&
gt;setShortcut(QString("Del"
));
actionRed-&
gt;setShortcut(QString("Alt+R"
));
actionGreen-&
gt;setShortcut(QString("Alt+G"
));
actionBlue-&
gt;setShortcut(QString("Alt+B"
));
actionAddCircle-&
gt;setShortcut(QString("Alt+C"
));
actionAddRectangle-&
gt;setShortcut(QString("Alt+L"
));
actionAddTriangle-&
gt;setShortcut(QString("Alt+T"
));
m_undoGroup =
new
QUndoGroup(this
);
undoView-&
gt;setGroup(m_undoGroup);
undoView-&
gt;setCleanIcon(QIcon(":/icons/ok.png"
));
QAction *
undoAction =
m_undoGroup-&
gt;createUndoAction(this
);
QAction *
redoAction =
m_undoGroup-&
gt;createRedoAction(this
);
undoAction-&
gt;setIcon(QIcon(":/icons/undo.png"
));
redoAction-&
gt;setIcon(QIcon(":/icons/redo.png"
));
menuShape-&
gt;insertAction(menuShape-&
gt;actions().at(0
), undoAction);
menuShape-&
gt;insertAction(undoAction, redoAction);
toolBar-&
gt;addAction(undoAction);
toolBar-&
gt;addAction(redoAction);
newDocument();
updateActions();
}
;
void
MainWindow::
updateActions()
{
Document *
doc =
currentDocument();
m_undoGroup-&
gt;setActiveStack(doc ==
0
? 0
: doc-&
gt;undoStack());
QString shapeName =
doc ==
0
? QString() : doc-&
gt;currentShapeName();
actionAddRobot-&
gt;setEnabled(doc !=
0
);
actionAddSnowman-&
gt;setEnabled(doc !=
0
);
actionAddCircle-&
gt;setEnabled(doc !=
0
);
actionAddRectangle-&
gt;setEnabled(doc !=
0
);
actionAddTriangle-&
gt;setEnabled(doc !=
0
);
actionClose-&
gt;setEnabled(doc !=
0
);
actionSave-&
gt;setEnabled(doc !=
0
&
amp;&
amp; !
doc-&
gt;undoStack()-&
gt;isClean());
undoLimit-&
gt;setEnabled(doc !=
0
&
amp;&
amp; doc-&
gt;undoStack()-&
gt;count() ==
0
);
if
(shapeName.isEmpty()) {
actionRed-&
gt;setEnabled(false
);
actionGreen-&
gt;setEnabled(false
);
actionBlue-&
gt;setEnabled(false
);
actionRemoveShape-&
gt;setEnabled(false
);
}
else
{
Shape shape =
doc-&
gt;shape(shapeName);
actionRed-&
gt;setEnabled(shape.color() !=
Qt::
red);
actionGreen-&
gt;setEnabled(shape.color() !=
Qt::
green);
actionBlue-&
gt;setEnabled(shape.color() !=
Qt::
blue);
actionRemoveShape-&
gt;setEnabled(true
);
}
if
(doc !=
0
) {
int
index =
documentTabs-&
gt;indexOf(doc);
Q_ASSERT(index !=
-
1
);
static
const
QIcon unsavedIcon(":/icons/filesave.png"
);
documentTabs-&
gt;setTabIcon(index, doc-&
gt;undoStack()-&
gt;isClean() ? QIcon() : unsavedIcon);
if
(doc-&
gt;undoStack()-&
gt;count() ==
0
)
doc-&
gt;undoStack()-&
gt;setUndoLimit(undoLimit-&
gt;value());
}
}
void
MainWindow::
openDocument()
{
QString fileName =
QFileDialog::
getOpenFileName(this
);
if
(fileName.isEmpty())
return
;
QFile file(fileName);
if
(!
file.open(QIODevice::
ReadOnly)) {
QMessageBox::
warning(this
,
tr("File error"
),
tr("Failed to open
\n
%1"
).arg(fileName));
return
;
}
QTextStream stream(&
amp;file);
Document *
doc =
new
Document();
if
(!
doc-&
gt;load(stream)) {
QMessageBox::
warning(this
,
tr("Parse error"
),
tr("Failed to parse
\n
%1"
).arg(fileName));
delete
doc;
return
;
}
doc-&
gt;setFileName(fileName);
addDocument(doc);
}
QString MainWindow::
fixedWindowTitle(const
Document *
doc) const
{
QString title =
doc-&
gt;fileName();
if
(title.isEmpty())
title =
tr("Unnamed"
);
else
title =
QFileInfo(title).fileName();
QString result;
for
(int
i =
0
; ; ++
i) {
result =
title;
if
(i &
gt; 0
)
result +=
QString::
number(i);
bool
unique =
true
;
for
(int
j =
0
; j &
lt; documentTabs-&
gt;count(); ++
j) {
const
QWidget *
widget =
documentTabs-&
gt;widget(j);
if
(widget ==
doc)
continue
;
if
(result ==
documentTabs-&
gt;tabText(j)) {
unique =
false
;
break
;
}
}
if
(unique)
break
;
}
return
result;
}
void
MainWindow::
addDocument(Document *
doc)
{
if
(documentTabs-&
gt;indexOf(doc) !=
-
1
)
return
;
m_undoGroup-&
gt;addStack(doc-&
gt;undoStack());
documentTabs-&
gt;addTab(doc, fixedWindowTitle(doc));
connect(doc, SIGNAL(currentShapeChanged(QString)), this
, SLOT(updateActions()));
connect(doc-&
gt;undoStack(), SIGNAL(indexChanged(int
)), this
, SLOT(updateActions()));
connect(doc-&
gt;undoStack(), SIGNAL(cleanChanged(bool
)), this
, SLOT(updateActions()));
setCurrentDocument(doc);
}
void
MainWindow::
setCurrentDocument(Document *
doc)
{
documentTabs-&
gt;setCurrentWidget(doc);
}
Document *
MainWindow::
currentDocument() const
{
return
qobject_cast&
lt;Document*&
gt;(documentTabs-&
gt;currentWidget());
}
void
MainWindow::
removeDocument(Document *
doc)
{
int
index =
documentTabs-&
gt;indexOf(doc);
if
(index ==
-
1
)
return
;
documentTabs-&
gt;removeTab(index);
m_undoGroup-&
gt;removeStack(doc-&
gt;undoStack());
disconnect(doc, SIGNAL(currentShapeChanged(QString)), this
, SLOT(updateActions()));
disconnect(doc-&
gt;undoStack(), SIGNAL(indexChanged(int
)), this
, SLOT(updateActions()));
disconnect(doc-&
gt;undoStack(), SIGNAL(cleanChanged(bool
)), this
, SLOT(updateActions()));
if
(documentTabs-&
gt;count() ==
0
) {
newDocument();
updateActions();
}
}
void
MainWindow::
saveDocument()
{
Document *
doc =
currentDocument();
if
(doc ==
0
)
return
;
for
(;;) {
QString fileName =
doc-&
gt;fileName();
if
(fileName.isEmpty())
fileName =
QFileDialog::
getSaveFileName(this
);
if
(fileName.isEmpty())
break
;
QFile file(fileName);
if
(!
file.open(QIODevice::
WriteOnly)) {
QMessageBox::
warning(this
,
tr("File error"
),
tr("Failed to open
\n
%1"
).arg(fileName));
doc-&
gt;setFileName(QString());
}
else
{
QTextStream stream(&
amp;file);
doc-&
gt;save(stream);
doc-&
gt;setFileName(fileName);
int
index =
documentTabs-&
gt;indexOf(doc);
Q_ASSERT(index !=
-
1
);
documentTabs-&
gt;setTabText(index, fixedWindowTitle(doc));
break
;
}
}
}
void
MainWindow::
closeDocument()
{
Document *
doc =
currentDocument();
if
(doc ==
0
)
return
;
if
(!
doc-&
gt;undoStack()-&
gt;isClean()) {
int
button
=
QMessageBox::
warning(this
,
tr("Unsaved changes"
),
tr("Would you like to save this document?"
),
QMessageBox::
Yes, QMessageBox::
No);
if
(button ==
QMessageBox::
Yes)
saveDocument();
}
removeDocument(doc);
delete
doc;
}
void
MainWindow::
newDocument()
{
addDocument(new
Document());
}
static
QColor randomColor()
{
int
r =
QRandomGenerator::
global()-&
gt;bounded(3
);
switch
(r) {
case
0
:
return
Qt::
red;
case
1
:
return
Qt::
green;
default
:
break
;
}
return
Qt::
blue;
}
static
QRect randomRect(const
QSize &
amp;s)
{
QSize min =
Shape::
minSize;
int
left =
(int
) ((0.0
+
s.width() -
min.width())*
(QRandomGenerator::
global()-&
gt;bounded(1.0
)));
int
top =
(int
) ((0.0
+
s.height() -
min.height())*
(QRandomGenerator::
global()-&
gt;bounded(1.0
)));
int
width =
(int
) ((0.0
+
s.width() -
left -
min.width())*
(QRandomGenerator::
global()-&
gt;bounded(1.0
))) +
min.width();
int
height =
(int
) ((0.0
+
s.height() -
top -
min.height())*
(QRandomGenerator::
global()-&
gt;bounded(1.0
))) +
min.height();
return
QRect(left, top, width, height);
}
void
MainWindow::
addShape()
{
Document *
doc =
currentDocument();
if
(doc ==
0
)
return
;
Shape::
Type type;
if
(sender() ==
actionAddCircle)
type =
Shape::
Circle;
else
if
(sender() ==
actionAddRectangle)
type =
Shape::
Rectangle;
else
if
(sender() ==
actionAddTriangle)
type =
Shape::
Triangle;
else
return
;
Shape newShape(type, randomColor(), randomRect(doc-&
gt;size()));
doc-&
gt;undoStack()-&
gt;push(new
AddShapeCommand(doc, newShape));
}
void
MainWindow::
removeShape()
{
Document *
doc =
currentDocument();
if
(doc ==
0
)
return
;
QString shapeName =
doc-&
gt;currentShapeName();
if
(shapeName.isEmpty())
return
;
doc-&
gt;undoStack()-&
gt;push(new
RemoveShapeCommand(doc, shapeName));
}
void
MainWindow::
setShapeColor()
{
Document *
doc =
currentDocument();
if
(doc ==
0
)
return
;
QString shapeName =
doc-&
gt;currentShapeName();
if
(shapeName.isEmpty())
return
;
QColor color;
if
(sender() ==
actionRed)
color =
Qt::
red;
else
if
(sender() ==
actionGreen)
color =
Qt::
green;
else
if
(sender() ==
actionBlue)
color =
Qt::
blue;
else
return
;
if
(color ==
doc-&
gt;shape(shapeName).color())
return
;
doc-&
gt;undoStack()-&
gt;push(new
SetShapeColorCommand(doc, shapeName, color));
}
void
MainWindow::
addSnowman()
{
Document *
doc =
currentDocument();
if
(doc ==
0
)
return
;
// Create a macro command using beginMacro() and endMacro()
doc-&
gt;undoStack()-&
gt;beginMacro(tr("Add snowman"
));
doc-&
gt;undoStack()-&
gt;push(new
AddShapeCommand(doc,
Shape(Shape::
Circle, Qt::
blue, QRect(51
, 30
, 97
, 95
))));
doc-&
gt;undoStack()-&
gt;push(new
AddShapeCommand(doc,
Shape(Shape::
Circle, Qt::
blue, QRect(27
, 123
, 150
, 133
))));
doc-&
gt;undoStack()-&
gt;push(new
AddShapeCommand(doc,
Shape(Shape::
Circle, Qt::
blue, QRect(11
, 253
, 188
, 146
))));
doc-&
gt;undoStack()-&
gt;endMacro();
}
void
MainWindow::
addRobot()
{
Document *
doc =
currentDocument();
if
(doc ==
0
)
return
;
// Compose a macro command by explicitly adding children to a parent command
QUndoCommand *
parent =
new
QUndoCommand(tr("Add robot"
));
new
AddShapeCommand(doc, Shape(Shape::
Rectangle, Qt::
green, QRect(115
, 15
, 81
, 70
)), parent);
new
AddShapeCommand(doc, Shape(Shape::
Rectangle, Qt::
green, QRect(82
, 89
, 148
, 188
)), parent);
new
AddShapeCommand(doc, Shape(Shape::
Rectangle, Qt::
green, QRect(76
, 280
, 80
, 165
)), parent);
new
AddShapeCommand(doc, Shape(Shape::
Rectangle, Qt::
green, QRect(163
, 280
, 80
, 164
)), parent);
new
AddShapeCommand(doc, Shape(Shape::
Circle, Qt::
blue, QRect(116
, 25
, 80
, 50
)), parent);
new
AddShapeCommand(doc, Shape(Shape::
Rectangle, Qt::
green, QRect(232
, 92
, 80
, 127
)), parent);
new
AddShapeCommand(doc, Shape(Shape::
Rectangle, Qt::
green, QRect(2
, 92
, 80
, 125
)), parent);
doc-&
gt;undoStack()-&
gt;push(parent);
}
void
MainWindow::
about()
{
QMessageBox::
about(this
, tr("About Undo"
), tr("The Undo demonstration shows how to use the Qt Undo framework."
));
}
void
MainWindow::
aboutQt()
{
QMessageBox::
aboutQt(this
, tr("About Qt"
));
}