Chris@16
|
1 /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@16
|
2
|
Chris@16
|
3 /*
|
Chris@16
|
4 A waveform viewer and audio annotation editor.
|
Chris@16
|
5 Chris Cannam, Queen Mary University of London, 2005-2006
|
Chris@16
|
6
|
Chris@16
|
7 This is experimental software. Not for distribution.
|
Chris@16
|
8 */
|
Chris@16
|
9
|
Chris@16
|
10 /*
|
Chris@16
|
11 This is a modified version of a source file from the Rosegarden
|
Chris@16
|
12 MIDI and audio sequencer and notation editor, copyright 2000-2006
|
Chris@16
|
13 Chris Cannam, distributed under the GNU General Public License.
|
Chris@16
|
14
|
Chris@16
|
15 This file contains traces of the KCommandHistory class from the KDE
|
Chris@16
|
16 project, copyright 2000 Werner Trobin and David Faure and
|
Chris@16
|
17 distributed under the GNU Lesser General Public License.
|
Chris@16
|
18 */
|
Chris@16
|
19
|
Chris@17
|
20 #include "CommandHistory.h"
|
Chris@16
|
21
|
Chris@16
|
22 #include "Command.h"
|
Chris@16
|
23
|
Chris@16
|
24 #include <QRegExp>
|
Chris@16
|
25 #include <QMenu>
|
Chris@16
|
26 #include <QToolBar>
|
Chris@16
|
27 #include <QString>
|
Chris@16
|
28
|
Chris@16
|
29 #include <iostream>
|
Chris@16
|
30
|
Chris@17
|
31 CommandHistory *CommandHistory::m_instance = 0;
|
Chris@17
|
32
|
Chris@17
|
33 CommandHistory::CommandHistory() :
|
Chris@16
|
34 m_undoLimit(50),
|
Chris@16
|
35 m_redoLimit(50),
|
Chris@46
|
36 m_menuLimit(15),
|
Chris@44
|
37 m_savedAt(0),
|
Chris@44
|
38 m_currentMacro(0),
|
Chris@44
|
39 m_executeMacro(false)
|
Chris@16
|
40 {
|
Chris@16
|
41 m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
|
Chris@16
|
42 m_undoAction->setShortcut(tr("Ctrl+Z"));
|
Chris@16
|
43 connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo()));
|
Chris@16
|
44
|
Chris@17
|
45 m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
|
Chris@17
|
46 connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo()));
|
Chris@17
|
47
|
Chris@16
|
48 m_undoMenu = new QMenu(tr("&Undo"));
|
Chris@17
|
49 m_undoMenuAction->setMenu(m_undoMenu);
|
Chris@16
|
50 connect(m_undoMenu, SIGNAL(triggered(QAction *)),
|
Chris@16
|
51 this, SLOT(undoActivated(QAction*)));
|
Chris@16
|
52
|
Chris@17
|
53 m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
|
Chris@16
|
54 m_redoAction->setShortcut(tr("Ctrl+Shift+Z"));
|
Chris@16
|
55 connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo()));
|
Chris@17
|
56
|
Chris@17
|
57 m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
|
Chris@17
|
58 connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo()));
|
Chris@16
|
59
|
Chris@16
|
60 m_redoMenu = new QMenu(tr("Re&do"));
|
Chris@17
|
61 m_redoMenuAction->setMenu(m_redoMenu);
|
Chris@16
|
62 connect(m_redoMenu, SIGNAL(triggered(QAction *)),
|
Chris@16
|
63 this, SLOT(redoActivated(QAction*)));
|
Chris@16
|
64 }
|
Chris@16
|
65
|
Chris@17
|
66 CommandHistory::~CommandHistory()
|
Chris@16
|
67 {
|
Chris@16
|
68 m_savedAt = -1;
|
Chris@16
|
69 clearStack(m_undoStack);
|
Chris@16
|
70 clearStack(m_redoStack);
|
Chris@16
|
71
|
Chris@16
|
72 delete m_undoMenu;
|
Chris@16
|
73 delete m_redoMenu;
|
Chris@16
|
74 }
|
Chris@16
|
75
|
Chris@17
|
76 CommandHistory *
|
Chris@17
|
77 CommandHistory::getInstance()
|
Chris@17
|
78 {
|
Chris@17
|
79 if (!m_instance) m_instance = new CommandHistory();
|
Chris@17
|
80 return m_instance;
|
Chris@17
|
81 }
|
Chris@17
|
82
|
Chris@16
|
83 void
|
Chris@17
|
84 CommandHistory::clear()
|
Chris@16
|
85 {
|
Chris@16
|
86 m_savedAt = -1;
|
Chris@16
|
87 clearStack(m_undoStack);
|
Chris@16
|
88 clearStack(m_redoStack);
|
Chris@16
|
89 updateActions();
|
Chris@16
|
90 }
|
Chris@16
|
91
|
Chris@16
|
92 void
|
Chris@17
|
93 CommandHistory::registerMenu(QMenu *menu)
|
Chris@16
|
94 {
|
Chris@16
|
95 menu->addAction(m_undoAction);
|
Chris@16
|
96 menu->addAction(m_redoAction);
|
Chris@16
|
97 }
|
Chris@16
|
98
|
Chris@16
|
99 void
|
Chris@17
|
100 CommandHistory::registerToolbar(QToolBar *toolbar)
|
Chris@16
|
101 {
|
Chris@17
|
102 toolbar->addAction(m_undoMenuAction);
|
Chris@17
|
103 toolbar->addAction(m_redoMenuAction);
|
Chris@16
|
104 }
|
Chris@16
|
105
|
Chris@16
|
106 void
|
Chris@17
|
107 CommandHistory::addCommand(Command *command, bool execute)
|
Chris@16
|
108 {
|
Chris@16
|
109 if (!command) return;
|
Chris@16
|
110
|
Chris@44
|
111 if (m_currentMacro) {
|
Chris@44
|
112 addToMacro(command);
|
Chris@44
|
113 return;
|
Chris@44
|
114 }
|
Chris@44
|
115
|
Chris@46
|
116 std::cerr << "MVCH::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
|
Chris@16
|
117
|
Chris@16
|
118 // We can't redo after adding a command
|
Chris@16
|
119 clearStack(m_redoStack);
|
Chris@16
|
120
|
Chris@16
|
121 // can we reach savedAt?
|
Chris@16
|
122 if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope
|
Chris@16
|
123
|
Chris@16
|
124 m_undoStack.push(command);
|
Chris@16
|
125 clipCommands();
|
Chris@16
|
126
|
Chris@16
|
127 if (execute) {
|
Chris@16
|
128 command->execute();
|
Chris@16
|
129 }
|
Chris@16
|
130
|
Chris@17
|
131 // Emit even if we aren't executing the command, because
|
Chris@17
|
132 // someone must have executed it for this to make any sense
|
Chris@17
|
133 emit commandExecuted();
|
Chris@17
|
134 emit commandExecuted(command);
|
Chris@17
|
135
|
Chris@16
|
136 updateActions();
|
Chris@16
|
137 }
|
Chris@16
|
138
|
Chris@16
|
139 void
|
Chris@44
|
140 CommandHistory::addToMacro(Command *command)
|
Chris@44
|
141 {
|
Chris@44
|
142 std::cerr << "MVCH::addToMacro: " << command->getName().toLocal8Bit().data() << std::endl;
|
Chris@44
|
143
|
Chris@44
|
144 if (m_executeMacro) command->execute();
|
Chris@44
|
145 m_currentMacro->addCommand(command);
|
Chris@44
|
146 }
|
Chris@44
|
147
|
Chris@44
|
148 void
|
Chris@44
|
149 CommandHistory::startCompoundOperation(QString name, bool execute)
|
Chris@44
|
150 {
|
Chris@44
|
151 if (m_currentMacro) {
|
Chris@44
|
152 std::cerr << "MVCH::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl;
|
Chris@44
|
153 std::cerr << "(name is " << m_currentMacro->getName().toLocal8Bit().data() << ")" << std::endl;
|
Chris@44
|
154 }
|
Chris@44
|
155
|
Chris@44
|
156 m_currentMacro = new MacroCommand(name);
|
Chris@44
|
157 m_executeMacro = execute;
|
Chris@44
|
158 }
|
Chris@44
|
159
|
Chris@44
|
160 void
|
Chris@44
|
161 CommandHistory::endCompoundOperation()
|
Chris@44
|
162 {
|
Chris@44
|
163 if (!m_currentMacro) {
|
Chris@44
|
164 std::cerr << "MVCH::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl;
|
Chris@44
|
165 }
|
Chris@44
|
166
|
Chris@44
|
167 Command *toAdd = m_currentMacro;
|
Chris@44
|
168 m_currentMacro = 0;
|
Chris@44
|
169
|
Chris@44
|
170 // We don't execute the macro command here, because we have been
|
Chris@44
|
171 // executing the individual commands as we went along if
|
Chris@44
|
172 // m_executeMacro was true.
|
Chris@44
|
173 addCommand(toAdd, false);
|
Chris@44
|
174 }
|
Chris@44
|
175
|
Chris@44
|
176 void
|
Chris@17
|
177 CommandHistory::addExecutedCommand(Command *command)
|
Chris@17
|
178 {
|
Chris@17
|
179 addCommand(command, false);
|
Chris@17
|
180 }
|
Chris@17
|
181
|
Chris@17
|
182 void
|
Chris@17
|
183 CommandHistory::addCommandAndExecute(Command *command)
|
Chris@17
|
184 {
|
Chris@17
|
185 addCommand(command, true);
|
Chris@17
|
186 }
|
Chris@17
|
187
|
Chris@17
|
188 void
|
Chris@17
|
189 CommandHistory::undo()
|
Chris@16
|
190 {
|
Chris@16
|
191 if (m_undoStack.empty()) return;
|
Chris@16
|
192
|
Chris@16
|
193 Command *command = m_undoStack.top();
|
Chris@16
|
194 command->unexecute();
|
Chris@16
|
195 emit commandExecuted();
|
Chris@17
|
196 emit commandUnexecuted(command);
|
Chris@16
|
197
|
Chris@16
|
198 m_redoStack.push(command);
|
Chris@16
|
199 m_undoStack.pop();
|
Chris@16
|
200
|
Chris@16
|
201 clipCommands();
|
Chris@16
|
202 updateActions();
|
Chris@16
|
203
|
Chris@16
|
204 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
|
Chris@16
|
205 }
|
Chris@16
|
206
|
Chris@16
|
207 void
|
Chris@17
|
208 CommandHistory::redo()
|
Chris@16
|
209 {
|
Chris@16
|
210 if (m_redoStack.empty()) return;
|
Chris@16
|
211
|
Chris@16
|
212 Command *command = m_redoStack.top();
|
Chris@16
|
213 command->execute();
|
Chris@16
|
214 emit commandExecuted();
|
Chris@16
|
215 emit commandExecuted(command);
|
Chris@16
|
216
|
Chris@16
|
217 m_undoStack.push(command);
|
Chris@16
|
218 m_redoStack.pop();
|
Chris@16
|
219 // no need to clip
|
Chris@16
|
220
|
Chris@16
|
221 updateActions();
|
Chris@41
|
222
|
Chris@41
|
223 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
|
Chris@16
|
224 }
|
Chris@16
|
225
|
Chris@16
|
226 void
|
Chris@17
|
227 CommandHistory::setUndoLimit(int limit)
|
Chris@16
|
228 {
|
Chris@16
|
229 if (limit > 0 && limit != m_undoLimit) {
|
Chris@16
|
230 m_undoLimit = limit;
|
Chris@16
|
231 clipCommands();
|
Chris@16
|
232 }
|
Chris@16
|
233 }
|
Chris@16
|
234
|
Chris@16
|
235 void
|
Chris@17
|
236 CommandHistory::setRedoLimit(int limit)
|
Chris@16
|
237 {
|
Chris@16
|
238 if (limit > 0 && limit != m_redoLimit) {
|
Chris@16
|
239 m_redoLimit = limit;
|
Chris@16
|
240 clipCommands();
|
Chris@16
|
241 }
|
Chris@16
|
242 }
|
Chris@16
|
243
|
Chris@16
|
244 void
|
Chris@46
|
245 CommandHistory::setMenuLimit(int limit)
|
Chris@46
|
246 {
|
Chris@46
|
247 m_menuLimit = limit;
|
Chris@46
|
248 updateActions();
|
Chris@46
|
249 }
|
Chris@46
|
250
|
Chris@46
|
251 void
|
Chris@17
|
252 CommandHistory::documentSaved()
|
Chris@16
|
253 {
|
Chris@16
|
254 m_savedAt = m_undoStack.size();
|
Chris@16
|
255 }
|
Chris@16
|
256
|
Chris@16
|
257 void
|
Chris@17
|
258 CommandHistory::clipCommands()
|
Chris@16
|
259 {
|
Chris@16
|
260 if ((int)m_undoStack.size() > m_undoLimit) {
|
Chris@16
|
261 m_savedAt -= (m_undoStack.size() - m_undoLimit);
|
Chris@16
|
262 }
|
Chris@16
|
263
|
Chris@16
|
264 clipStack(m_undoStack, m_undoLimit);
|
Chris@16
|
265 clipStack(m_redoStack, m_redoLimit);
|
Chris@16
|
266 }
|
Chris@16
|
267
|
Chris@16
|
268 void
|
Chris@17
|
269 CommandHistory::clipStack(CommandStack &stack, int limit)
|
Chris@16
|
270 {
|
Chris@16
|
271 int i;
|
Chris@16
|
272
|
Chris@16
|
273 if ((int)stack.size() > limit) {
|
Chris@16
|
274
|
Chris@16
|
275 CommandStack tempStack;
|
Chris@16
|
276
|
Chris@16
|
277 for (i = 0; i < limit; ++i) {
|
Chris@16
|
278 Command *command = stack.top();
|
Chris@17
|
279 std::cerr << "MVCH::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
|
Chris@16
|
280 tempStack.push(stack.top());
|
Chris@16
|
281 stack.pop();
|
Chris@16
|
282 }
|
Chris@16
|
283
|
Chris@16
|
284 clearStack(stack);
|
Chris@16
|
285
|
Chris@16
|
286 for (i = 0; i < m_undoLimit; ++i) {
|
Chris@16
|
287 stack.push(tempStack.top());
|
Chris@16
|
288 tempStack.pop();
|
Chris@16
|
289 }
|
Chris@16
|
290 }
|
Chris@16
|
291 }
|
Chris@16
|
292
|
Chris@16
|
293 void
|
Chris@17
|
294 CommandHistory::clearStack(CommandStack &stack)
|
Chris@16
|
295 {
|
Chris@16
|
296 while (!stack.empty()) {
|
Chris@16
|
297 Command *command = stack.top();
|
Chris@46
|
298 // Not safe to call getName() on a command about to be deleted
|
Chris@46
|
299 std::cerr << "MVCH::clearStack: About to delete command " << command << std::endl;
|
Chris@16
|
300 delete command;
|
Chris@16
|
301 stack.pop();
|
Chris@16
|
302 }
|
Chris@16
|
303 }
|
Chris@16
|
304
|
Chris@16
|
305 void
|
Chris@17
|
306 CommandHistory::undoActivated(QAction *action)
|
Chris@16
|
307 {
|
Chris@16
|
308 int pos = m_actionCounts[action];
|
Chris@16
|
309 for (int i = 0; i <= pos; ++i) {
|
Chris@16
|
310 undo();
|
Chris@16
|
311 }
|
Chris@16
|
312 }
|
Chris@16
|
313
|
Chris@16
|
314 void
|
Chris@17
|
315 CommandHistory::redoActivated(QAction *action)
|
Chris@16
|
316 {
|
Chris@16
|
317 int pos = m_actionCounts[action];
|
Chris@16
|
318 for (int i = 0; i <= pos; ++i) {
|
Chris@16
|
319 redo();
|
Chris@16
|
320 }
|
Chris@16
|
321 }
|
Chris@16
|
322
|
Chris@16
|
323 void
|
Chris@17
|
324 CommandHistory::updateActions()
|
Chris@16
|
325 {
|
Chris@16
|
326 m_actionCounts.clear();
|
Chris@16
|
327
|
Chris@16
|
328 for (int undo = 0; undo <= 1; ++undo) {
|
Chris@16
|
329
|
Chris@17
|
330 QAction *action(undo ? m_undoAction : m_redoAction);
|
Chris@17
|
331 QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction);
|
Chris@16
|
332 QMenu *menu(undo ? m_undoMenu : m_redoMenu);
|
Chris@16
|
333 CommandStack &stack(undo ? m_undoStack : m_redoStack);
|
Chris@16
|
334
|
Chris@17
|
335 if (stack.empty()) {
|
Chris@17
|
336
|
Chris@17
|
337 QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo"));
|
Chris@17
|
338
|
Chris@17
|
339 action->setEnabled(false);
|
Chris@17
|
340 action->setText(text);
|
Chris@17
|
341
|
Chris@17
|
342 menuAction->setEnabled(false);
|
Chris@17
|
343 menuAction->setText(text);
|
Chris@17
|
344
|
Chris@17
|
345 } else {
|
Chris@17
|
346
|
Chris@17
|
347 action->setEnabled(true);
|
Chris@17
|
348 menuAction->setEnabled(true);
|
Chris@17
|
349
|
Chris@17
|
350 QString commandName = stack.top()->getName();
|
Chris@17
|
351 commandName.replace(QRegExp("&"), "");
|
Chris@17
|
352
|
Chris@17
|
353 QString text = (undo ? tr("&Undo %1") : tr("Re&do %1"))
|
Chris@17
|
354 .arg(commandName);
|
Chris@17
|
355
|
Chris@17
|
356 action->setText(text);
|
Chris@17
|
357 menuAction->setText(text);
|
Chris@17
|
358 }
|
Chris@17
|
359
|
Chris@16
|
360 menu->clear();
|
Chris@16
|
361
|
Chris@16
|
362 CommandStack tempStack;
|
Chris@16
|
363 int j = 0;
|
Chris@16
|
364
|
Chris@46
|
365 while (j < m_menuLimit && !stack.empty()) {
|
Chris@16
|
366
|
Chris@16
|
367 Command *command = stack.top();
|
Chris@16
|
368 tempStack.push(command);
|
Chris@16
|
369 stack.pop();
|
Chris@16
|
370
|
Chris@17
|
371 QString commandName = command->getName();
|
Chris@16
|
372 commandName.replace(QRegExp("&"), "");
|
Chris@16
|
373
|
Chris@16
|
374 QString text;
|
Chris@16
|
375 if (undo) text = tr("&Undo %1").arg(commandName);
|
Chris@16
|
376 else text = tr("Re&do %1").arg(commandName);
|
Chris@16
|
377
|
Chris@16
|
378 QAction *action = menu->addAction(text);
|
Chris@16
|
379 m_actionCounts[action] = j++;
|
Chris@16
|
380 }
|
Chris@16
|
381
|
Chris@16
|
382 while (!tempStack.empty()) {
|
Chris@16
|
383 stack.push(tempStack.top());
|
Chris@16
|
384 tempStack.pop();
|
Chris@16
|
385 }
|
Chris@16
|
386 }
|
Chris@16
|
387 }
|
Chris@16
|
388
|