Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qundostack.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtCore/qdebug.h>
5#include "qundostack.h"
6#if QT_CONFIG(undogroup)
7#include "qundogroup.h"
8#endif
9#include "qundostack_p.h"
10
12
77 : QUndoCommand(parent)
78{
80}
81
93{
94 d = new QUndoCommandPrivate;
95 if (parent != nullptr)
96 parent->d->child_list.append(this);
97}
98
106{
108 delete d;
109}
110
124{
125 return d->obsolete;
126}
127
136void QUndoCommand::setObsolete(bool obsolete)
137{
138 d->obsolete = obsolete;
139}
140
157{
158 return -1;
159}
160
181{
182 Q_UNUSED(command);
183 return false;
184}
185
198{
199 for (int i = 0; i < d->child_list.size(); ++i)
200 d->child_list.at(i)->redo();
201}
202
216{
217 for (int i = d->child_list.size() - 1; i >= 0; --i)
218 d->child_list.at(i)->undo();
219}
220
231{
232 return d->text;
233}
234
248{
249 return d->actionText;
250}
251
268{
269 int cdpos = text.indexOf(u'\n');
270 if (cdpos > 0) {
271 d->text = text.left(cdpos);
272 d->actionText = text.mid(cdpos + 1);
273 } else {
274 d->text = text;
275 d->actionText = text;
276 }
277}
278
288{
289 return d->child_list.size();
290}
291
301{
302 if (index < 0 || index >= d->child_list.size())
303 return nullptr;
304 return d->child_list.at(index);
305}
306
307#if QT_CONFIG(undostack)
308
415void QUndoStackPrivate::setIndex(int idx, bool clean)
416{
417 Q_Q(QUndoStack);
418
419 bool was_clean = index == clean_index;
420
421 if (idx != index) {
422 index = idx;
423 emit q->indexChanged(index);
424 emit q->canUndoChanged(q->canUndo());
425 emit q->undoTextChanged(q->undoText());
426 emit q->canRedoChanged(q->canRedo());
427 emit q->redoTextChanged(q->redoText());
428 }
429
430 if (clean)
431 clean_index = index;
432
433 bool is_clean = index == clean_index;
434 if (is_clean != was_clean)
435 emit q->cleanChanged(is_clean);
436}
437
445bool QUndoStackPrivate::checkUndoLimit()
446{
447 if (undo_limit <= 0 || !macro_stack.isEmpty() || undo_limit >= command_list.size())
448 return false;
449
450 int del_count = command_list.size() - undo_limit;
451
452 for (int i = 0; i < del_count; ++i)
453 delete command_list.takeFirst();
454
455 index -= del_count;
456 if (clean_index != -1) {
457 if (clean_index < del_count)
458 clean_index = -1; // we've deleted the clean command
459 else
460 clean_index -= del_count;
461 }
462
463 return true;
464}
465
474QUndoStack::QUndoStack(QObject *parent)
475 : QObject(*(new QUndoStackPrivate), parent)
476{
477#if QT_CONFIG(undogroup)
478 if (QUndoGroup *group = qobject_cast<QUndoGroup*>(parent))
479 group->addStack(this);
480#endif
481}
482
490QUndoStack::~QUndoStack()
491{
492#if QT_CONFIG(undogroup)
493 Q_D(QUndoStack);
494 if (d->group != nullptr)
495 d->group->removeStack(this);
496#endif
497 clear();
498}
499
513void QUndoStack::clear()
514{
515 Q_D(QUndoStack);
516
517 if (d->command_list.isEmpty())
518 return;
519
520 bool was_clean = isClean();
521
522 d->macro_stack.clear();
523 qDeleteAll(d->command_list);
524 d->command_list.clear();
525
526 d->index = 0;
527 d->clean_index = 0;
528
529 emit indexChanged(0);
530 emit canUndoChanged(false);
531 emit undoTextChanged(QString());
532 emit canRedoChanged(false);
533 emit redoTextChanged(QString());
534
535 if (!was_clean)
536 emit cleanChanged(true);
537}
538
567void QUndoStack::push(QUndoCommand *cmd)
568{
569 Q_D(QUndoStack);
570 if (!cmd->isObsolete())
571 cmd->redo();
572
573 bool macro = !d->macro_stack.isEmpty();
574
575 QUndoCommand *cur = nullptr;
576 if (macro) {
577 QUndoCommand *macro_cmd = d->macro_stack.constLast();
578 if (!macro_cmd->d->child_list.isEmpty())
579 cur = macro_cmd->d->child_list.constLast();
580 } else {
581 if (d->index > 0)
582 cur = d->command_list.at(d->index - 1);
583 while (d->index < d->command_list.size())
584 delete d->command_list.takeLast();
585 if (d->clean_index > d->index)
586 d->clean_index = -1; // we've deleted the clean state
587 }
588
589 bool try_merge = cur != nullptr
590 && cur->id() != -1
591 && cur->id() == cmd->id()
592 && (macro || d->index != d->clean_index);
593
594 if (try_merge && cur->mergeWith(cmd)) {
595 delete cmd;
596
597 if (macro) {
598 if (cur->isObsolete())
599 delete d->macro_stack.constLast()->d->child_list.takeLast();
600 } else {
601 if (cur->isObsolete()) {
602 delete d->command_list.takeLast();
603
604 d->setIndex(d->index - 1, false);
605 } else {
606 emit indexChanged(d->index);
607 emit canUndoChanged(canUndo());
608 emit undoTextChanged(undoText());
609 emit canRedoChanged(canRedo());
610 emit redoTextChanged(redoText());
611 }
612 }
613 } else if (cmd->isObsolete()) {
614 delete cmd; // command should be deleted and NOT added to the stack
615 } else {
616 if (macro) {
617 d->macro_stack.constLast()->d->child_list.append(cmd);
618 } else {
619 d->command_list.append(cmd);
620 d->checkUndoLimit();
621 d->setIndex(d->index + 1, false);
622 }
623 }
624}
625
639void QUndoStack::setClean()
640{
641 Q_D(QUndoStack);
642 if (Q_UNLIKELY(!d->macro_stack.isEmpty())) {
643 qWarning("QUndoStack::setClean(): cannot set clean in the middle of a macro");
644 return;
645 }
646
647 d->setIndex(d->index, true);
648}
649
667void QUndoStack::resetClean()
668{
669 Q_D(QUndoStack);
670 const bool was_clean = isClean();
671 d->clean_index = -1;
672 if (was_clean)
673 emit cleanChanged(false);
674}
675
693bool QUndoStack::isClean() const
694{
695 Q_D(const QUndoStack);
696 if (!d->macro_stack.isEmpty())
697 return false;
698 return d->clean_index == d->index;
699}
700
713int QUndoStack::cleanIndex() const
714{
715 Q_D(const QUndoStack);
716 return d->clean_index;
717}
718
734void QUndoStack::undo()
735{
736 Q_D(QUndoStack);
737 if (d->index == 0)
738 return;
739
740 if (Q_UNLIKELY(!d->macro_stack.isEmpty())) {
741 qWarning("QUndoStack::undo(): cannot undo in the middle of a macro");
742 return;
743 }
744
745 int idx = d->index - 1;
746 QUndoCommand *cmd = d->command_list.at(idx);
747
748 if (!cmd->isObsolete())
749 cmd->undo();
750
751 if (cmd->isObsolete()) { // A separate check is done b/c the undo command may set obsolete flag
752 delete d->command_list.takeAt(idx);
753
754 if (d->clean_index > idx)
755 resetClean();
756 }
757
758 d->setIndex(idx, false);
759}
760
776void QUndoStack::redo()
777{
778 Q_D(QUndoStack);
779 if (d->index == d->command_list.size())
780 return;
781
782 if (Q_UNLIKELY(!d->macro_stack.isEmpty())) {
783 qWarning("QUndoStack::redo(): cannot redo in the middle of a macro");
784 return;
785 }
786
787 int idx = d->index;
788 QUndoCommand *cmd = d->command_list.at(idx);
789
790 if (!cmd->isObsolete())
791 cmd->redo(); // A separate check is done b/c the undo command may set obsolete flag
792
793 if (cmd->isObsolete()) {
794 delete d->command_list.takeAt(idx);
795
796 if (d->clean_index > idx)
797 resetClean();
798 } else {
799 d->setIndex(d->index + 1, false);
800 }
801}
802
810int QUndoStack::count() const
811{
812 Q_D(const QUndoStack);
813 return d->command_list.size();
814}
815
824int QUndoStack::index() const
825{
826 Q_D(const QUndoStack);
827 return d->index;
828}
829
838void QUndoStack::setIndex(int idx)
839{
840 Q_D(QUndoStack);
841 if (Q_UNLIKELY(!d->macro_stack.isEmpty())) {
842 qWarning("QUndoStack::setIndex(): cannot set index in the middle of a macro");
843 return;
844 }
845
846 if (idx < 0)
847 idx = 0;
848 else if (idx > d->command_list.size())
849 idx = d->command_list.size();
850
851 int i = d->index;
852 while (i < idx) {
853 QUndoCommand *cmd = d->command_list.at(i);
854
855 if (!cmd->isObsolete())
856 cmd->redo(); // A separate check is done b/c the undo command may set obsolete flag
857
858 if (cmd->isObsolete()) {
859 delete d->command_list.takeAt(i);
860
861 if (d->clean_index > i)
862 resetClean();
863
864 idx--; // Subtract from idx because we removed a command
865 } else {
866 i++;
867 }
868 }
869
870 while (i > idx) {
871 QUndoCommand *cmd = d->command_list.at(--i);
872
873 cmd->undo();
874 if (cmd->isObsolete()) {
875 delete d->command_list.takeAt(i);
876
877 if (d->clean_index > i)
878 resetClean();
879 }
880 }
881
882 d->setIndex(idx, false);
883}
884
907bool QUndoStack::canUndo() const
908{
909 Q_D(const QUndoStack);
910 if (!d->macro_stack.isEmpty())
911 return false;
912 return d->index > 0;
913}
914
937bool QUndoStack::canRedo() const
938{
939 Q_D(const QUndoStack);
940 if (!d->macro_stack.isEmpty())
941 return false;
942 return d->index < d->command_list.size();
943}
944
962QString QUndoStack::undoText() const
963{
964 Q_D(const QUndoStack);
965 if (!d->macro_stack.isEmpty())
966 return QString();
967 if (d->index > 0)
968 return d->command_list.at(d->index - 1)->actionText();
969 return QString();
970}
971
989QString QUndoStack::redoText() const
990{
991 Q_D(const QUndoStack);
992 if (!d->macro_stack.isEmpty())
993 return QString();
994 if (d->index < d->command_list.size())
995 return d->command_list.at(d->index)->actionText();
996 return QString();
997}
998
999#ifndef QT_NO_ACTION
1000
1006void QUndoStackPrivate::setPrefixedText(QAction *action, const QString &prefix, const QString &defaultText, const QString &text)
1007{
1008 if (defaultText.isEmpty()) {
1009 QString s = prefix;
1010 if (!prefix.isEmpty() && !text.isEmpty())
1011 s.append(u' ');
1012 s.append(text);
1013 action->setText(s);
1014 } else {
1015 if (text.isEmpty())
1016 action->setText(defaultText);
1017 else
1018 action->setText(prefix.arg(text));
1019 }
1020};
1021
1036QAction *QUndoStack::createUndoAction(QObject *parent, const QString &prefix) const
1037{
1038 QAction *action = new QAction(parent);
1039 action->setEnabled(canUndo());
1040
1041 QString effectivePrefix = prefix;
1042 QString defaultText;
1043 if (prefix.isEmpty()) {
1044 effectivePrefix = tr("Undo %1");
1045 defaultText = tr("Undo", "Default text for undo action");
1046 }
1047
1048 QUndoStackPrivate::setPrefixedText(action, effectivePrefix, defaultText, undoText());
1049
1050 connect(this, &QUndoStack::canUndoChanged, action, &QAction::setEnabled);
1051 connect(this, &QUndoStack::undoTextChanged, action, [=](const QString &text) {
1052 QUndoStackPrivate::setPrefixedText(action, effectivePrefix, defaultText, text);
1053 });
1054 connect(action, &QAction::triggered, this, &QUndoStack::undo);
1055
1056 return action;
1057}
1058
1073QAction *QUndoStack::createRedoAction(QObject *parent, const QString &prefix) const
1074{
1075 QAction *action = new QAction(parent);
1076 action->setEnabled(canRedo());
1077
1078 QString effectivePrefix = prefix;
1079 QString defaultText;
1080 if (prefix.isEmpty()) {
1081 effectivePrefix = tr("Redo %1");
1082 defaultText = tr("Redo", "Default text for redo action");
1083 }
1084
1085 QUndoStackPrivate::setPrefixedText(action, effectivePrefix, defaultText, redoText());
1086
1087 connect(this, &QUndoStack::canRedoChanged, action, &QAction::setEnabled);
1088 connect(this, &QUndoStack::redoTextChanged, action, [=](const QString &text) {
1089 QUndoStackPrivate::setPrefixedText(action, effectivePrefix, defaultText, text);
1090 });
1091 connect(action, &QAction::triggered, this, &QUndoStack::redo);
1092
1093 return action;
1094}
1095
1096#endif // QT_NO_ACTION
1097
1128void QUndoStack::beginMacro(const QString &text)
1129{
1130 Q_D(QUndoStack);
1131 QUndoCommand *cmd = new QUndoCommand();
1132 cmd->setText(text);
1133
1134 if (d->macro_stack.isEmpty()) {
1135 while (d->index < d->command_list.size())
1136 delete d->command_list.takeLast();
1137 if (d->clean_index > d->index)
1138 d->clean_index = -1; // we've deleted the clean state
1139 d->command_list.append(cmd);
1140 } else {
1141 d->macro_stack.constLast()->d->child_list.append(cmd);
1142 }
1143 d->macro_stack.append(cmd);
1144
1145 if (d->macro_stack.size() == 1) {
1146 emit canUndoChanged(false);
1147 emit undoTextChanged(QString());
1148 emit canRedoChanged(false);
1149 emit redoTextChanged(QString());
1150 }
1151}
1152
1162void QUndoStack::endMacro()
1163{
1164 Q_D(QUndoStack);
1165 if (Q_UNLIKELY(d->macro_stack.isEmpty())) {
1166 qWarning("QUndoStack::endMacro(): no matching beginMacro()");
1167 return;
1168 }
1169
1170 d->macro_stack.removeLast();
1171
1172 if (d->macro_stack.isEmpty()) {
1173 d->checkUndoLimit();
1174 d->setIndex(d->index + 1, false);
1175 }
1176}
1177
1190const QUndoCommand *QUndoStack::command(int index) const
1191{
1192 Q_D(const QUndoStack);
1193
1194 if (index < 0 || index >= d->command_list.size())
1195 return nullptr;
1196 return d->command_list.at(index);
1197}
1198
1205QString QUndoStack::text(int idx) const
1206{
1207 Q_D(const QUndoStack);
1208
1209 if (idx < 0 || idx >= d->command_list.size())
1210 return QString();
1211 return d->command_list.at(idx)->text();
1212}
1213
1229void QUndoStack::setUndoLimit(int limit)
1230{
1231 Q_D(QUndoStack);
1232
1233 if (Q_UNLIKELY(!d->command_list.isEmpty())) {
1234 qWarning("QUndoStack::setUndoLimit(): an undo limit can only be set when the stack is empty");
1235 return;
1236 }
1237
1238 if (limit == d->undo_limit)
1239 return;
1240 d->undo_limit = limit;
1241 d->checkUndoLimit();
1242}
1243
1244int QUndoStack::undoLimit() const
1245{
1246 Q_D(const QUndoStack);
1247
1248 return d->undo_limit;
1249}
1250
1268void QUndoStack::setActive(bool active)
1269{
1270#if !QT_CONFIG(undogroup)
1271 Q_UNUSED(active);
1272#else
1273 Q_D(QUndoStack);
1274
1275 if (d->group != nullptr) {
1276 if (active)
1277 d->group->setActiveStack(this);
1278 else if (d->group->activeStack() == this)
1279 d->group->setActiveStack(nullptr);
1280 }
1281#endif
1282}
1283
1284bool QUndoStack::isActive() const
1285{
1286#if !QT_CONFIG(undogroup)
1287 return true;
1288#else
1289 Q_D(const QUndoStack);
1290 return d->group == nullptr || d->group->activeStack() == this;
1291#endif
1292}
1293
1351
1352#include "moc_qundostack.cpp"
1353
1354#endif // QT_CONFIG(undostack)
The QAction class provides an abstraction for user commands that can be added to different user inter...
Definition qaction.h:30
void triggered(bool checked=false)
This signal is emitted when an action is activated by the user; for example, when the user clicks a m...
void setText(const QString &text)
Definition qaction.cpp:611
void setEnabled(bool)
Definition qaction.cpp:927
qsizetype size() const noexcept
Definition qlist.h:397
const T & constLast() const noexcept
Definition qlist.h:650
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
value_type takeLast()
Definition qlist.h:567
void append(parameter_type t)
Definition qlist.h:458
\inmodule QtCore
Definition qobject.h:103
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString left(qsizetype n) const &
Definition qstring.h:363
qsizetype indexOf(QLatin1StringView s, qsizetype from=0, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.cpp:4517
QString mid(qsizetype position, qsizetype n=-1) const &
Definition qstring.cpp:5300
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
QString arg(qlonglong a, int fieldwidth=0, int base=10, QChar fillChar=u' ') const
Definition qstring.cpp:8870
QString & append(QChar c)
Definition qstring.cpp:3252
QList< QUndoCommand * > child_list
virtual int id() const
Returns the ID of this command.
bool isObsolete() const
QString actionText() const
virtual bool mergeWith(const QUndoCommand *other)
Attempts to merge this command with command.
virtual ~QUndoCommand()
Destroys the QUndoCommand object and all child commands.
virtual void undo()
Reverts a change to the document.
void setText(const QString &text)
Sets the command's text to be the text specified.
const QUndoCommand * child(int index) const
QString text() const
Returns a short text string describing what this command does; for example, "insert text".
virtual void redo()
Applies a change to the document.
QUndoCommand(QUndoCommand *parent=nullptr)
Constructs a QUndoCommand object with parent parent.
void setObsolete(bool obsolete)
int childCount() const
The QUndoGroup class is a group of QUndoStack objects.
Definition qundogroup.h:20
b clear()
QString text
qDeleteAll(list.begin(), list.end())
Combined button and popup list for selecting options.
#define Q_UNLIKELY(x)
#define qWarning
Definition qlogging.h:166
GLuint index
[2]
GLboolean GLuint group
GLdouble s
[6]
Definition qopenglext.h:235
GLint limit
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
#define tr(X)
#define emit
#define Q_UNUSED(x)
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)