CuteLogger
Fast and simple logging solution for Qt based applications
undohelper.h
1/*
2 * Copyright (c) 2015-2026 Meltytech, LLC
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#ifndef UNDOHELPER_H
19#define UNDOHELPER_H
20
21#include "models/multitrackmodel.h"
22
23#include <MltPlaylist.h>
24#include <QList>
25#include <QMap>
26#include <QSet>
27#include <QString>
28
29// Records the state of the affected tracks before a timeline edit and reverts them on undo.
30// Two strategies, selected per command via setHints():
31//
32// - RestoreTracks: each affected track's playlist XML is snapshotted whole and, on undo, the
33// live track is cleared and rebuilt verbatim from it. Clip content, in/out points, blanks,
34// groups, and transition wiring all restore together and always consistently. Use for
35// commands that mutate clip content in place, move clips across tracks, or otherwise cannot
36// be expressed as a per-clip diff.
37//
38// - NoHints / SkipXML: a per-clip diff (by uuid) is replayed surgically, restoring only the
39// clips that actually moved, resized, were added, or were removed. This avoids clearing and
40// re-inserting an entire track (O(clips)) for a single-clip edit. Under NoHints the track
41// snapshot is also taken, so a removed or content-changed clip is rebuilt from it; under
42// SkipXML no snapshot is taken and the command promises its undo needs only in/out resizes
43// and moves (no clip rebuild). SkipXML and RestoreTracks are mutually exclusive.
44//
45// The fine-grained path cannot detect a pure in-place content change (same uuid, index, and
46// in/out): any command that mutates clip content without a structural change must use
47// RestoreTracks, or its undo will silently do nothing.
48class UndoHelper
49{
50public:
51 // Mutually exclusive; compare with ==, never bitwise.
52 enum OptimizationHints { NoHints, SkipXML, RestoreTracks };
53
54 UndoHelper(MultitrackModel &model);
55
56 // trackScope: the set of track indexes that this command can possibly affect. When
57 // non-empty, capture is restricted to just these tracks (and none are excluded as
58 // locked, since the caller is asserting it will touch them), which avoids the
59 // O(total clips in project) cost of scanning every track for commands that are known
60 // in advance to touch only a few of them. Leave empty (the default) to fall back to
61 // scanning every unlocked track, e.g. when the affected tracks are not known until
62 // after the command has partially run (see MoveClipCommand). Locked tracks are
63 // skipped in that fallback because every ripple path in MultitrackModel already
64 // refuses to mutate a locked track.
65 void recordBeforeState(const QSet<int> &trackScope = QSet<int>());
66 void recordAfterState();
67 void undoChanges();
68 void setHints(OptimizationHints hints);
69 void setText(const QString &text) { m_text = text; };
70
71 // Capture this clip's own XML in recordBeforeState so it can be restored from that small
72 // snapshot on undo, instead of parsing the whole track's snapshot to reach one clip. Call
73 // before recordBeforeState() for each clip a command removes or rewrites in place.
74 void storeXmlForClip(const QUuid &uid);
75
76private:
77 void debugPrintState(const QString &title);
78 bool survivingClipsInOrder() const;
79 void restoreAffectedTracks();
80 void promoteUuids(Mlt::Playlist &playlist);
81 void demoteUuids(Mlt::Playlist &playlist);
82
83 enum ChangeFlags {
84 NoChange = 0x0,
85 ClipInfoModified = 0x1,
86 Moved = 0x2,
87 Removed = 0x4,
88 };
89
90 struct Info
91 {
92 int oldTrackIndex = -1;
93 int oldClipIndex = -1;
94 int newTrackIndex = -1;
95 int newClipIndex = -1;
96 bool isBlank = false;
97 int frame_in = -1;
98 int frame_out = -1;
99 int in_delta = 0;
100 int out_delta = 0;
101 int group = -1;
102 int changes = NoChange;
103 };
104
105 void insertRestoredClip(Mlt::Playlist &playlist,
106 int clipIndex,
107 const QUuid &uid,
108 const Info &info,
109 Mlt::Producer *entry);
110
111 QMap<int, QString> m_beforeXml;
112 QMap<QUuid, Info> m_state;
113 QList<QUuid> m_insertedOrder;
114 QList<QUuid> m_clipsAdded;
115 QSet<QUuid> m_xmlClips;
116 QMap<QUuid, QString> m_clipXml;
117 QSet<int> m_affectedTracks;
118 QSet<int> m_scannedTracks;
119 QSet<int> m_trackScope;
120 MultitrackModel &m_model;
121 OptimizationHints m_hints = NoHints;
122 QString m_text;
123};
124
125#endif // UNDOHELPER_H