1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Rosegarden
5     A MIDI and audio sequencer and musical notation editor.
6     Copyright 2000-2021 the Rosegarden development team.
7 
8     Other copyrights also apply to some parts of this work.  Please
9     see the AUTHORS file and individual file headers for details.
10 
11     This program is free software; you can redistribute it and/or
12     modify it under the terms of the GNU General Public License as
13     published by the Free Software Foundation; either version 2 of the
14     License, or (at your option) any later version.  See the file
15     COPYING included with this distribution for more information.
16 */
17 
18 #ifndef RG_COMMANDHISTORY_H
19 #define RG_COMMANDHISTORY_H
20 
21 #include <QObject>
22 #include <QString>
23 
24 #include <stack>
25 #include <set>
26 #include <map>
27 
28 class QAction;
29 class QMenu;
30 class QToolBar;
31 class QTimer;
32 
33 namespace Rosegarden
34 {
35 
36 class Command;
37 class MacroCommand;
38 class ActionFileClient;
39 
40 /**
41  * The CommandHistory class stores a list of executed
42  * commands and maintains Undo and Redo actions synchronised with
43  * those commands.
44  *
45  * CommandHistory allows you to associate more than one Undo
46  * and Redo menu or toolbar with the same command history, and it
47  * keeps them all up-to-date at once.  This makes it effective in
48  * systems where multiple views may be editing the same data.
49  */
50 class CommandHistory : public QObject
51 {
52     Q_OBJECT
53 
54 public:
55     ~CommandHistory() override;
56 
57     static CommandHistory *getInstance();
58 
59     void clear();
60 
61     // These are done by the .rc file these days.
62     //void registerMenu(QMenu *menu);
63     //void registerToolbar(QToolBar *toolbar);
64 
65     /// Add a command to the command history.
66     /**
67      * The command will normally be executed before being added; but
68      * if a compound operation is in use (see startCompoundOperation
69      * below), the execute status of the compound operation will
70      * determine whether the command is executed or not.
71      */
72     void addCommand(Command *command);
73 
74     /// Add a command to the command history.
75     /**
76      * If execute is true, the command will be executed before being
77      * added.  Otherwise it will be assumed to have been already
78      * executed -- a command should not be added to the history unless
79      * its work has actually been done somehow!
80      *
81      * If a compound operation is in use (see startCompoundOperation
82      * below), the execute value passed to this method will override
83      * the execute status of the compound operation.  In this way it's
84      * possible to have a compound operation mixing both to-execute
85      * and pre-executed commands.
86      *
87      * If bundle is true, the command will be a candidate for bundling
88      * with any adjacent bundleable commands that have the same name,
89      * into a single compound command.  This is useful for small
90      * commands that may be executed repeatedly altering the same data
91      * (e.g. type text, set a parameter) whose number and extent is
92      * not known in advance.  The bundle parameter will be ignored if
93      * a compound operation is already in use.
94      */
95     void addCommand(Command *command, bool execute, bool bundle = false);
96 
97     /// Return the maximum number of items in the undo history.
98     int getUndoLimit() const { return m_undoLimit; }
99 
100     /// Set the maximum number of items in the undo history.
101     void setUndoLimit(int limit);
102 
103     /// Return the maximum number of items in the redo history.
104     int getRedoLimit() const { return m_redoLimit; }
105 
106     /// Set the maximum number of items in the redo history.
107     void setRedoLimit(int limit);
108 
109     /// Return the maximum number of items visible in undo and redo menus.
110     int getMenuLimit() const { return m_menuLimit; }
111 
112     /// Set the maximum number of items in the menus.
113     void setMenuLimit(int limit);
114 
115     /// Return the time after which a bundle will be closed if nothing is added.
116     int getBundleTimeout() const { return m_bundleTimeout; }
117 
118     /// Set the time after which a bundle will be closed if nothing is added.
119     void setBundleTimeout(int msec);
120 
121     /// Start recording commands to batch up into a single compound command.
122     void startCompoundOperation(QString name, bool execute);
123 
124     /// Finish recording commands and store the compound command.
125     void endCompoundOperation();
126 
127     /// Enable/Disable undo (during playback).
128     void enableUndo(bool enable);
129 
130 public slots:
131     /**
132      * Checkpoint function that should be called when the document is
133      * saved.  If the undo/redo stack later returns to the point at
134      * which the document was saved, the documentRestored signal will
135      * be emitted.
136      */
137     virtual void documentSaved();
138 
139     /**
140      * Add a command to the history that has already been executed,
141      * without executing it again.  Equivalent to addCommand(command, false).
142      */
143     void addExecutedCommand(Command *);
144 
145     /**
146      * Add a command to the history and also execute it.  Equivalent
147      * to addCommand(command, true).
148      */
149     void addCommandAndExecute(Command *);
150 
151     void undo();
152     void redo();
153 
154 protected slots:
155     void undoActivated(QAction *);
156     void redoActivated(QAction *);
157     void bundleTimerTimeout();
158 
159 signals:
160     /**
161      * Emitted just before commandExecuted() so that linked segments can
162      * update their siblings.
163      */
164     void updateLinkedSegments(Command *);
165 
166     /**
167      * Emitted whenever a command has just been executed or
168      * unexecuted, whether by addCommand, undo, or redo.  Note in
169      * particular that this is emitted by both undo and redo.
170      */
171     void commandExecuted();
172 
173     /**
174      * Emitted whenever a command has just been executed, whether by
175      * addCommand or redo.  Note that this is not emitted by undo,
176      * which emits commandUnexecuted(Command *) instead.
177      */
178     void commandExecuted(Command *);
179 
180     /**
181      * Emitted whenever a command has just been unexecuted, whether by
182      * addCommand or undo.
183      */
184     void commandUnexecuted(Command *);
185 
186     /**
187      * Emitted when the undo/redo stack has reached the same state at
188      * which the documentSaved slot was last called.
189      */
190     void documentRestored();
191 
192 
193 protected:
194     CommandHistory();
195     static CommandHistory *m_instance;
196 
197     // Actions and Menus
198 
199     /// Edit > Undo on all menus.
200     QAction *m_undoAction;
201     /// Edit > Redo on all menus.
202     QAction *m_redoAction;
203     /// RosegardenMainWindow toolbar undo.
204     QAction *m_undoMenuAction;
205     /// RosegardenMainWindow toolbar redo.
206     QAction *m_redoMenuAction;
207 
208     QMenu *m_undoMenu;
209     QMenu *m_redoMenu;
210 
211     std::map<QAction *, int> m_actionCounts;
212 
213     void updateActions();
214 
215     // Command Stacks
216     typedef std::stack<Command *> CommandStack;
217     CommandStack m_undoStack;
218     CommandStack m_redoStack;
219     void clipStack(CommandStack &stack, int limit);
220     void clearStack(CommandStack &stack);
221     void clipCommands();
222 
223     int m_undoLimit;
224     int m_redoLimit;
225     int m_menuLimit;
226     int m_savedAt;
227 
228     // Compound
229     MacroCommand *m_currentCompound;
230     bool m_executeCompound;
231     void addToCompound(Command *command, bool execute);
232 
233     // Bundle
234     MacroCommand *m_currentBundle;
235     QString m_currentBundleName;
236     QTimer *m_bundleTimer;
237     int m_bundleTimeout;
238     void addToBundle(Command *command, bool execute);
239     void closeBundle();
240 
241     /// Enable/Disable undo (during playback).
242     bool m_enableUndo;
243 
244 };
245 
246 }
247 
248 #endif
249