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 "qundogroup.h" |
5 | #include "qundostack.h" |
6 | #include "qundostack_p.h" |
7 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | class QUndoGroupPrivate : public QObjectPrivate |
11 | { |
12 | Q_DECLARE_PUBLIC(QUndoGroup) |
13 | public: |
14 | QUndoGroupPrivate() : active(nullptr) {} |
15 | |
16 | QUndoStack *active; |
17 | QList<QUndoStack*> stack_list; |
18 | }; |
19 | |
20 | /*! |
21 | \class QUndoGroup |
22 | \brief The QUndoGroup class is a group of QUndoStack objects. |
23 | \since 4.2 |
24 | \inmodule QtGui |
25 | |
26 | For an overview of the Qt's undo framework, see the |
27 | \l{qundo.html}{overview}. |
28 | |
29 | An application often has multiple undo stacks, one for each opened document. At the |
30 | same time, an application usually has one undo action and one redo action, which |
31 | triggers undo or redo in the active document. |
32 | |
33 | QUndoGroup is a group of QUndoStack objects, one of which may be active. It has |
34 | an undo() and redo() slot, which calls QUndoStack::undo() and QUndoStack::redo() |
35 | for the active stack. It also has the functions createUndoAction() and createRedoAction(). |
36 | The actions returned by these functions behave in the same way as those returned by |
37 | QUndoStack::createUndoAction() and QUndoStack::createRedoAction() of the active |
38 | stack. |
39 | |
40 | Stacks are added to a group with addStack() and removed with removeStack(). A stack |
41 | is implicitly added to a group when it is created with the group as its parent |
42 | QObject. |
43 | |
44 | It is the programmer's responsibility to specify which stack is active by |
45 | calling QUndoStack::setActive(), usually when the associated document window receives focus. |
46 | The active stack may also be set with setActiveStack(), and is returned by activeStack(). |
47 | |
48 | When a stack is added to a group using addStack(), the group does not take ownership |
49 | of the stack. This means the stack has to be deleted separately from the group. When |
50 | a stack is deleted, it is automatically removed from a group. A stack may belong to |
51 | only one group. Adding it to another group will cause it to be removed from the previous |
52 | group. |
53 | |
54 | A QUndoGroup is also useful in conjunction with QUndoView. If a QUndoView is |
55 | set to watch a group using QUndoView::setGroup(), it will update itself to display |
56 | the active stack. |
57 | */ |
58 | |
59 | /*! |
60 | Creates an empty QUndoGroup object with parent \a parent. |
61 | |
62 | \sa addStack() |
63 | */ |
64 | |
65 | QUndoGroup::QUndoGroup(QObject *parent) |
66 | : QObject(*new QUndoGroupPrivate(), parent) |
67 | { |
68 | } |
69 | |
70 | /*! |
71 | Destroys the QUndoGroup. |
72 | */ |
73 | QUndoGroup::~QUndoGroup() |
74 | { |
75 | // Ensure all QUndoStacks no longer refer to this group. |
76 | Q_D(QUndoGroup); |
77 | QList<QUndoStack *>::iterator it = d->stack_list.begin(); |
78 | QList<QUndoStack *>::iterator end = d->stack_list.end(); |
79 | while (it != end) { |
80 | (*it)->d_func()->group = nullptr; |
81 | ++it; |
82 | } |
83 | } |
84 | |
85 | /*! |
86 | Adds \a stack to this group. The group does not take ownership of the stack. Another |
87 | way of adding a stack to a group is by specifying the group as the stack's parent |
88 | QObject in QUndoStack::QUndoStack(). In this case, the stack is deleted when the |
89 | group is deleted, in the usual manner of QObjects. |
90 | |
91 | \sa removeStack(), stacks(), QUndoStack::QUndoStack() |
92 | */ |
93 | |
94 | void QUndoGroup::addStack(QUndoStack *stack) |
95 | { |
96 | Q_D(QUndoGroup); |
97 | |
98 | if (d->stack_list.contains(t: stack)) |
99 | return; |
100 | d->stack_list.append(t: stack); |
101 | |
102 | if (QUndoGroup *other = stack->d_func()->group) |
103 | other->removeStack(stack); |
104 | stack->d_func()->group = this; |
105 | } |
106 | |
107 | /*! |
108 | Removes \a stack from this group. If the stack was the active stack in the group, |
109 | the active stack becomes 0. |
110 | |
111 | \sa addStack(), stacks(), QUndoStack::~QUndoStack() |
112 | */ |
113 | |
114 | void QUndoGroup::removeStack(QUndoStack *stack) |
115 | { |
116 | Q_D(QUndoGroup); |
117 | |
118 | if (d->stack_list.removeAll(t: stack) == 0) |
119 | return; |
120 | if (stack == d->active) |
121 | setActiveStack(nullptr); |
122 | stack->d_func()->group = nullptr; |
123 | } |
124 | |
125 | /*! |
126 | Returns a list of stacks in this group. |
127 | |
128 | \sa addStack(), removeStack() |
129 | */ |
130 | |
131 | QList<QUndoStack*> QUndoGroup::stacks() const |
132 | { |
133 | Q_D(const QUndoGroup); |
134 | return d->stack_list; |
135 | } |
136 | |
137 | /*! |
138 | Sets the active stack of this group to \a stack. |
139 | |
140 | If the stack is not a member of this group, this function does nothing. |
141 | |
142 | Synonymous with calling QUndoStack::setActive() on \a stack. |
143 | |
144 | The actions returned by createUndoAction() and createRedoAction() will now behave |
145 | in the same way as those returned by \a stack's QUndoStack::createUndoAction() |
146 | and QUndoStack::createRedoAction(). |
147 | |
148 | \sa QUndoStack::setActive(), activeStack() |
149 | */ |
150 | |
151 | void QUndoGroup::setActiveStack(QUndoStack *stack) |
152 | { |
153 | Q_D(QUndoGroup); |
154 | if (d->active == stack) |
155 | return; |
156 | |
157 | if (d->active != nullptr) { |
158 | disconnect(sender: d->active, SIGNAL(canUndoChanged(bool)), |
159 | receiver: this, SIGNAL(canUndoChanged(bool))); |
160 | disconnect(sender: d->active, SIGNAL(undoTextChanged(QString)), |
161 | receiver: this, SIGNAL(undoTextChanged(QString))); |
162 | disconnect(sender: d->active, SIGNAL(canRedoChanged(bool)), |
163 | receiver: this, SIGNAL(canRedoChanged(bool))); |
164 | disconnect(sender: d->active, SIGNAL(redoTextChanged(QString)), |
165 | receiver: this, SIGNAL(redoTextChanged(QString))); |
166 | disconnect(sender: d->active, SIGNAL(indexChanged(int)), |
167 | receiver: this, SIGNAL(indexChanged(int))); |
168 | disconnect(sender: d->active, SIGNAL(cleanChanged(bool)), |
169 | receiver: this, SIGNAL(cleanChanged(bool))); |
170 | } |
171 | |
172 | d->active = stack; |
173 | |
174 | if (d->active == nullptr) { |
175 | emit canUndoChanged(canUndo: false); |
176 | emit undoTextChanged(undoText: QString()); |
177 | emit canRedoChanged(canRedo: false); |
178 | emit redoTextChanged(redoText: QString()); |
179 | emit cleanChanged(clean: true); |
180 | emit indexChanged(idx: 0); |
181 | } else { |
182 | connect(sender: d->active, SIGNAL(canUndoChanged(bool)), |
183 | receiver: this, SIGNAL(canUndoChanged(bool))); |
184 | connect(sender: d->active, SIGNAL(undoTextChanged(QString)), |
185 | receiver: this, SIGNAL(undoTextChanged(QString))); |
186 | connect(sender: d->active, SIGNAL(canRedoChanged(bool)), |
187 | receiver: this, SIGNAL(canRedoChanged(bool))); |
188 | connect(sender: d->active, SIGNAL(redoTextChanged(QString)), |
189 | receiver: this, SIGNAL(redoTextChanged(QString))); |
190 | connect(sender: d->active, SIGNAL(indexChanged(int)), |
191 | receiver: this, SIGNAL(indexChanged(int))); |
192 | connect(sender: d->active, SIGNAL(cleanChanged(bool)), |
193 | receiver: this, SIGNAL(cleanChanged(bool))); |
194 | emit canUndoChanged(canUndo: d->active->canUndo()); |
195 | emit undoTextChanged(undoText: d->active->undoText()); |
196 | emit canRedoChanged(canRedo: d->active->canRedo()); |
197 | emit redoTextChanged(redoText: d->active->redoText()); |
198 | emit cleanChanged(clean: d->active->isClean()); |
199 | emit indexChanged(idx: d->active->index()); |
200 | } |
201 | |
202 | emit activeStackChanged(stack: d->active); |
203 | } |
204 | |
205 | /*! |
206 | Returns the active stack of this group. |
207 | |
208 | If none of the stacks are active, or if the group is empty, this function |
209 | returns \nullptr. |
210 | |
211 | \sa setActiveStack(), QUndoStack::setActive() |
212 | */ |
213 | |
214 | QUndoStack *QUndoGroup::activeStack() const |
215 | { |
216 | Q_D(const QUndoGroup); |
217 | return d->active; |
218 | } |
219 | |
220 | #ifndef QT_NO_ACTION |
221 | |
222 | /*! |
223 | Creates an undo QAction object with parent \a parent. |
224 | |
225 | Triggering this action will cause a call to QUndoStack::undo() on the active stack. |
226 | The text of this action will always be the text of the command which will be undone |
227 | in the next call to undo(), prefixed by \a prefix. If there is no command available |
228 | for undo, if the group is empty or if none of the stacks are active, this action will |
229 | be disabled. |
230 | |
231 | If \a prefix is empty, the default template "Undo %1" is used instead of prefix. |
232 | Before Qt 4.8, the prefix "Undo" was used by default. |
233 | |
234 | \sa createRedoAction(), canUndo(), QUndoCommand::text() |
235 | */ |
236 | |
237 | QAction *QUndoGroup::createUndoAction(QObject *parent, const QString &prefix) const |
238 | { |
239 | QAction *action = new QAction(parent); |
240 | action->setEnabled(canUndo()); |
241 | |
242 | QString effectivePrefix = prefix; |
243 | QString defaultText; |
244 | if (prefix.isEmpty()) { |
245 | effectivePrefix = tr(s: "Undo %1" ); |
246 | defaultText = tr(s: "Undo" , c: "Default text for undo action" ); |
247 | } |
248 | |
249 | QUndoStackPrivate::setPrefixedText(action, prefix: effectivePrefix, defaultText, text: undoText()); |
250 | |
251 | connect(sender: this, signal: &QUndoGroup::canUndoChanged, context: action, slot: &QAction::setEnabled); |
252 | connect(sender: this, signal: &QUndoGroup::undoTextChanged, context: action, slot: [=](const QString &text) { |
253 | QUndoStackPrivate::setPrefixedText(action, prefix: effectivePrefix, defaultText, text); |
254 | }); |
255 | connect(sender: action, signal: &QAction::triggered, context: this, slot: &QUndoGroup::undo); |
256 | |
257 | return action; |
258 | } |
259 | |
260 | /*! |
261 | Creates an redo QAction object with parent \a parent. |
262 | |
263 | Triggering this action will cause a call to QUndoStack::redo() on the active stack. |
264 | The text of this action will always be the text of the command which will be redone |
265 | in the next call to redo(), prefixed by \a prefix. If there is no command available |
266 | for redo, if the group is empty or if none of the stacks are active, this action will |
267 | be disabled. |
268 | |
269 | If \a prefix is empty, the default template "Redo %1" is used instead of prefix. |
270 | Before Qt 4.8, the prefix "Redo" was used by default. |
271 | |
272 | \sa createUndoAction(), canRedo(), QUndoCommand::text() |
273 | */ |
274 | |
275 | QAction *QUndoGroup::createRedoAction(QObject *parent, const QString &prefix) const |
276 | { |
277 | QAction *action = new QAction(parent); |
278 | action->setEnabled(canRedo()); |
279 | |
280 | QString effectivePrefix = prefix; |
281 | QString defaultText; |
282 | if (prefix.isEmpty()) { |
283 | effectivePrefix = tr(s: "Redo %1" ); |
284 | defaultText = tr(s: "Redo" , c: "Default text for redo action" ); |
285 | } |
286 | |
287 | QUndoStackPrivate::setPrefixedText(action, prefix: effectivePrefix, defaultText, text: redoText()); |
288 | |
289 | connect(sender: this, signal: &QUndoGroup::canRedoChanged, context: action, slot: &QAction::setEnabled); |
290 | connect(sender: this, signal: &QUndoGroup::redoTextChanged, context: action, slot: [=](const QString &text) { |
291 | QUndoStackPrivate::setPrefixedText(action, prefix: effectivePrefix, defaultText, text); |
292 | }); |
293 | connect(sender: action, signal: &QAction::triggered, context: this, slot: &QUndoGroup::redo); |
294 | return action; |
295 | } |
296 | |
297 | #endif // QT_NO_ACTION |
298 | |
299 | /*! |
300 | Calls QUndoStack::undo() on the active stack. |
301 | |
302 | If none of the stacks are active, or if the group is empty, this function |
303 | does nothing. |
304 | |
305 | \sa redo(), canUndo(), setActiveStack() |
306 | */ |
307 | |
308 | void QUndoGroup::undo() |
309 | { |
310 | Q_D(QUndoGroup); |
311 | if (d->active != nullptr) |
312 | d->active->undo(); |
313 | } |
314 | |
315 | /*! |
316 | Calls QUndoStack::redo() on the active stack. |
317 | |
318 | If none of the stacks are active, or if the group is empty, this function |
319 | does nothing. |
320 | |
321 | \sa undo(), canRedo(), setActiveStack() |
322 | */ |
323 | |
324 | |
325 | void QUndoGroup::redo() |
326 | { |
327 | Q_D(QUndoGroup); |
328 | if (d->active != nullptr) |
329 | d->active->redo(); |
330 | } |
331 | |
332 | /*! |
333 | Returns the value of the active stack's QUndoStack::canUndo(). |
334 | |
335 | If none of the stacks are active, or if the group is empty, this function |
336 | returns \c false. |
337 | |
338 | \sa canRedo(), setActiveStack() |
339 | */ |
340 | |
341 | bool QUndoGroup::canUndo() const |
342 | { |
343 | Q_D(const QUndoGroup); |
344 | return d->active != nullptr && d->active->canUndo(); |
345 | } |
346 | |
347 | /*! |
348 | Returns the value of the active stack's QUndoStack::canRedo(). |
349 | |
350 | If none of the stacks are active, or if the group is empty, this function |
351 | returns \c false. |
352 | |
353 | \sa canUndo(), setActiveStack() |
354 | */ |
355 | |
356 | bool QUndoGroup::canRedo() const |
357 | { |
358 | Q_D(const QUndoGroup); |
359 | return d->active != nullptr && d->active->canRedo(); |
360 | } |
361 | |
362 | /*! |
363 | Returns the value of the active stack's QUndoStack::undoText(). |
364 | |
365 | If none of the stacks are active, or if the group is empty, this function |
366 | returns an empty string. |
367 | |
368 | \sa redoText(), setActiveStack() |
369 | */ |
370 | |
371 | QString QUndoGroup::undoText() const |
372 | { |
373 | Q_D(const QUndoGroup); |
374 | return d->active == nullptr ? QString() : d->active->undoText(); |
375 | } |
376 | |
377 | /*! |
378 | Returns the value of the active stack's QUndoStack::redoText(). |
379 | |
380 | If none of the stacks are active, or if the group is empty, this function |
381 | returns an empty string. |
382 | |
383 | \sa undoText(), setActiveStack() |
384 | */ |
385 | |
386 | QString QUndoGroup::redoText() const |
387 | { |
388 | Q_D(const QUndoGroup); |
389 | return d->active == nullptr ? QString() : d->active->redoText(); |
390 | } |
391 | |
392 | /*! |
393 | Returns the value of the active stack's QUndoStack::isClean(). |
394 | |
395 | If none of the stacks are active, or if the group is empty, this function |
396 | returns \c true. |
397 | |
398 | \sa setActiveStack() |
399 | */ |
400 | |
401 | bool QUndoGroup::isClean() const |
402 | { |
403 | Q_D(const QUndoGroup); |
404 | return d->active == nullptr || d->active->isClean(); |
405 | } |
406 | |
407 | /*! \fn void QUndoGroup::activeStackChanged(QUndoStack *stack) |
408 | |
409 | This signal is emitted whenever the active stack of the group changes. This can happen |
410 | when setActiveStack() or QUndoStack::setActive() is called, or when the active stack |
411 | is removed form the group. \a stack is the new active stack. If no stack is active, |
412 | \a stack is 0. |
413 | |
414 | \sa setActiveStack(), QUndoStack::setActive() |
415 | */ |
416 | |
417 | /*! \fn void QUndoGroup::indexChanged(int idx) |
418 | |
419 | This signal is emitted whenever the active stack emits QUndoStack::indexChanged() |
420 | or the active stack changes. |
421 | |
422 | \a idx is the new current index, or 0 if the active stack is 0. |
423 | |
424 | \sa QUndoStack::indexChanged(), setActiveStack() |
425 | */ |
426 | |
427 | /*! \fn void QUndoGroup::cleanChanged(bool clean) |
428 | |
429 | This signal is emitted whenever the active stack emits QUndoStack::cleanChanged() |
430 | or the active stack changes. |
431 | |
432 | \a clean is the new state, or true if the active stack is 0. |
433 | |
434 | \sa QUndoStack::cleanChanged(), setActiveStack() |
435 | */ |
436 | |
437 | /*! \fn void QUndoGroup::canUndoChanged(bool canUndo) |
438 | |
439 | This signal is emitted whenever the active stack emits QUndoStack::canUndoChanged() |
440 | or the active stack changes. |
441 | |
442 | \a canUndo is the new state, or false if the active stack is 0. |
443 | |
444 | \sa QUndoStack::canUndoChanged(), setActiveStack() |
445 | */ |
446 | |
447 | /*! \fn void QUndoGroup::canRedoChanged(bool canRedo) |
448 | |
449 | This signal is emitted whenever the active stack emits QUndoStack::canRedoChanged() |
450 | or the active stack changes. |
451 | |
452 | \a canRedo is the new state, or false if the active stack is 0. |
453 | |
454 | \sa QUndoStack::canRedoChanged(), setActiveStack() |
455 | */ |
456 | |
457 | /*! \fn void QUndoGroup::undoTextChanged(const QString &undoText) |
458 | |
459 | This signal is emitted whenever the active stack emits QUndoStack::undoTextChanged() |
460 | or the active stack changes. |
461 | |
462 | \a undoText is the new state, or an empty string if the active stack is 0. |
463 | |
464 | \sa QUndoStack::undoTextChanged(), setActiveStack() |
465 | */ |
466 | |
467 | /*! \fn void QUndoGroup::redoTextChanged(const QString &redoText) |
468 | |
469 | This signal is emitted whenever the active stack emits QUndoStack::redoTextChanged() |
470 | or the active stack changes. |
471 | |
472 | \a redoText is the new state, or an empty string if the active stack is 0. |
473 | |
474 | \sa QUndoStack::redoTextChanged(), setActiveStack() |
475 | */ |
476 | |
477 | QT_END_NAMESPACE |
478 | |
479 | #include "moc_qundogroup.cpp" |
480 | |