annotate base/CommandHistory.cpp @ 47:bac8b14ab355

* Add menu for re-adding existing layers * Fix layer tree window so that it at least approximates correct * Add bundled operations in command history, for use with things like multiple consecutive changes to a parameter value * Disambiguate plugins that happen to have identical descriptions * Add spectral centroid plugin (could use some parameters!) * Some other fixes
author Chris Cannam
date Fri, 17 Mar 2006 17:38:28 +0000
parents 5364a9d338a2
children 39ae3dee27b9
rev   line source
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@47 28 #include <QTimer>
Chris@16 29
Chris@16 30 #include <iostream>
Chris@16 31
Chris@17 32 CommandHistory *CommandHistory::m_instance = 0;
Chris@17 33
Chris@17 34 CommandHistory::CommandHistory() :
Chris@16 35 m_undoLimit(50),
Chris@16 36 m_redoLimit(50),
Chris@46 37 m_menuLimit(15),
Chris@44 38 m_savedAt(0),
Chris@47 39 m_currentCompound(0),
Chris@47 40 m_executeCompound(false),
Chris@47 41 m_currentBundle(0),
Chris@47 42 m_bundleTimer(0),
Chris@47 43 m_bundleTimeout(5000)
Chris@16 44 {
Chris@16 45 m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
Chris@16 46 m_undoAction->setShortcut(tr("Ctrl+Z"));
Chris@16 47 connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo()));
Chris@16 48
Chris@17 49 m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
Chris@17 50 connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo()));
Chris@17 51
Chris@16 52 m_undoMenu = new QMenu(tr("&Undo"));
Chris@17 53 m_undoMenuAction->setMenu(m_undoMenu);
Chris@16 54 connect(m_undoMenu, SIGNAL(triggered(QAction *)),
Chris@16 55 this, SLOT(undoActivated(QAction*)));
Chris@16 56
Chris@17 57 m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
Chris@16 58 m_redoAction->setShortcut(tr("Ctrl+Shift+Z"));
Chris@16 59 connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo()));
Chris@17 60
Chris@17 61 m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
Chris@17 62 connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo()));
Chris@16 63
Chris@16 64 m_redoMenu = new QMenu(tr("Re&do"));
Chris@17 65 m_redoMenuAction->setMenu(m_redoMenu);
Chris@16 66 connect(m_redoMenu, SIGNAL(triggered(QAction *)),
Chris@16 67 this, SLOT(redoActivated(QAction*)));
Chris@16 68 }
Chris@16 69
Chris@17 70 CommandHistory::~CommandHistory()
Chris@16 71 {
Chris@16 72 m_savedAt = -1;
Chris@16 73 clearStack(m_undoStack);
Chris@16 74 clearStack(m_redoStack);
Chris@16 75
Chris@16 76 delete m_undoMenu;
Chris@16 77 delete m_redoMenu;
Chris@16 78 }
Chris@16 79
Chris@17 80 CommandHistory *
Chris@17 81 CommandHistory::getInstance()
Chris@17 82 {
Chris@17 83 if (!m_instance) m_instance = new CommandHistory();
Chris@17 84 return m_instance;
Chris@17 85 }
Chris@17 86
Chris@16 87 void
Chris@17 88 CommandHistory::clear()
Chris@16 89 {
Chris@47 90 std::cerr << "CommandHistory::clear()" << std::endl;
Chris@47 91 closeBundle();
Chris@16 92 m_savedAt = -1;
Chris@16 93 clearStack(m_undoStack);
Chris@16 94 clearStack(m_redoStack);
Chris@16 95 updateActions();
Chris@16 96 }
Chris@16 97
Chris@16 98 void
Chris@17 99 CommandHistory::registerMenu(QMenu *menu)
Chris@16 100 {
Chris@16 101 menu->addAction(m_undoAction);
Chris@16 102 menu->addAction(m_redoAction);
Chris@16 103 }
Chris@16 104
Chris@16 105 void
Chris@17 106 CommandHistory::registerToolbar(QToolBar *toolbar)
Chris@16 107 {
Chris@17 108 toolbar->addAction(m_undoMenuAction);
Chris@17 109 toolbar->addAction(m_redoMenuAction);
Chris@16 110 }
Chris@16 111
Chris@16 112 void
Chris@47 113 CommandHistory::addCommand(Command *command, bool execute, bool bundle)
Chris@16 114 {
Chris@16 115 if (!command) return;
Chris@16 116
Chris@47 117 if (m_currentCompound) {
Chris@47 118 addToCompound(command);
Chris@44 119 return;
Chris@44 120 }
Chris@44 121
Chris@47 122 if (bundle) {
Chris@47 123 addToBundle(command, execute);
Chris@47 124 return;
Chris@47 125 } else if (m_currentBundle) {
Chris@47 126 closeBundle();
Chris@47 127 }
Chris@47 128
Chris@47 129 std::cerr << "CommandHistory::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
Chris@16 130
Chris@16 131 // We can't redo after adding a command
Chris@47 132 std::cerr << "CommandHistory::clearing redo stack" << std::endl;
Chris@16 133 clearStack(m_redoStack);
Chris@16 134
Chris@16 135 // can we reach savedAt?
Chris@16 136 if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope
Chris@16 137
Chris@16 138 m_undoStack.push(command);
Chris@16 139 clipCommands();
Chris@16 140
Chris@16 141 if (execute) {
Chris@16 142 command->execute();
Chris@16 143 }
Chris@16 144
Chris@17 145 // Emit even if we aren't executing the command, because
Chris@17 146 // someone must have executed it for this to make any sense
Chris@17 147 emit commandExecuted();
Chris@17 148 emit commandExecuted(command);
Chris@17 149
Chris@16 150 updateActions();
Chris@16 151 }
Chris@16 152
Chris@16 153 void
Chris@47 154 CommandHistory::addToBundle(Command *command, bool execute)
Chris@44 155 {
Chris@47 156 if (m_currentBundle) {
Chris@47 157 if (!command || (command->getName() != m_currentBundleName)) {
Chris@47 158 closeBundle();
Chris@47 159 }
Chris@47 160 }
Chris@44 161
Chris@47 162 if (!command) return;
Chris@47 163
Chris@47 164 if (!m_currentBundle) {
Chris@47 165 // need to addCommand before setting m_currentBundle, as addCommand
Chris@47 166 // with bundle false will reset m_currentBundle to 0
Chris@47 167 MacroCommand *mc = new MacroCommand(command->getName());
Chris@47 168 addCommand(mc, false);
Chris@47 169 m_currentBundle = mc;
Chris@47 170 m_currentBundleName = command->getName();
Chris@47 171 }
Chris@47 172
Chris@47 173 if (execute) command->execute();
Chris@47 174 m_currentBundle->addCommand(command);
Chris@47 175
Chris@47 176 delete m_bundleTimer;
Chris@47 177 m_bundleTimer = new QTimer(this);
Chris@47 178 connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout()));
Chris@47 179 m_bundleTimer->start(m_bundleTimeout);
Chris@47 180 }
Chris@47 181
Chris@47 182 void
Chris@47 183 CommandHistory::closeBundle()
Chris@47 184 {
Chris@47 185 m_currentBundle = 0;
Chris@47 186 m_currentBundleName = "";
Chris@47 187 }
Chris@47 188
Chris@47 189 void
Chris@47 190 CommandHistory::bundleTimerTimeout()
Chris@47 191 {
Chris@47 192 closeBundle();
Chris@47 193 }
Chris@47 194
Chris@47 195 void
Chris@47 196 CommandHistory::addToCompound(Command *command)
Chris@47 197 {
Chris@47 198 std::cerr << "CommandHistory::addToCompound: " << command->getName().toLocal8Bit().data() << std::endl;
Chris@47 199
Chris@47 200 if (m_executeCompound) command->execute();
Chris@47 201 m_currentCompound->addCommand(command);
Chris@44 202 }
Chris@44 203
Chris@44 204 void
Chris@44 205 CommandHistory::startCompoundOperation(QString name, bool execute)
Chris@44 206 {
Chris@47 207 if (m_currentCompound) {
Chris@47 208 std::cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl;
Chris@47 209 std::cerr << "(name is " << m_currentCompound->getName().toLocal8Bit().data() << ")" << std::endl;
Chris@44 210 }
Chris@47 211
Chris@47 212 closeBundle();
Chris@47 213
Chris@47 214 m_currentCompound = new MacroCommand(name);
Chris@47 215 m_executeCompound = execute;
Chris@44 216 }
Chris@44 217
Chris@44 218 void
Chris@44 219 CommandHistory::endCompoundOperation()
Chris@44 220 {
Chris@47 221 if (!m_currentCompound) {
Chris@47 222 std::cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl;
Chris@44 223 }
Chris@44 224
Chris@47 225 Command *toAdd = m_currentCompound;
Chris@47 226 m_currentCompound = 0;
Chris@44 227
Chris@44 228 // We don't execute the macro command here, because we have been
Chris@44 229 // executing the individual commands as we went along if
Chris@47 230 // m_executeCompound was true.
Chris@44 231 addCommand(toAdd, false);
Chris@44 232 }
Chris@44 233
Chris@44 234 void
Chris@17 235 CommandHistory::addExecutedCommand(Command *command)
Chris@17 236 {
Chris@17 237 addCommand(command, false);
Chris@17 238 }
Chris@17 239
Chris@17 240 void
Chris@17 241 CommandHistory::addCommandAndExecute(Command *command)
Chris@17 242 {
Chris@17 243 addCommand(command, true);
Chris@17 244 }
Chris@17 245
Chris@17 246 void
Chris@17 247 CommandHistory::undo()
Chris@16 248 {
Chris@16 249 if (m_undoStack.empty()) return;
Chris@16 250
Chris@47 251 closeBundle();
Chris@47 252
Chris@16 253 Command *command = m_undoStack.top();
Chris@16 254 command->unexecute();
Chris@16 255 emit commandExecuted();
Chris@17 256 emit commandUnexecuted(command);
Chris@16 257
Chris@16 258 m_redoStack.push(command);
Chris@16 259 m_undoStack.pop();
Chris@16 260
Chris@16 261 clipCommands();
Chris@16 262 updateActions();
Chris@16 263
Chris@16 264 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@16 265 }
Chris@16 266
Chris@16 267 void
Chris@17 268 CommandHistory::redo()
Chris@16 269 {
Chris@16 270 if (m_redoStack.empty()) return;
Chris@16 271
Chris@47 272 closeBundle();
Chris@47 273
Chris@16 274 Command *command = m_redoStack.top();
Chris@16 275 command->execute();
Chris@16 276 emit commandExecuted();
Chris@16 277 emit commandExecuted(command);
Chris@16 278
Chris@16 279 m_undoStack.push(command);
Chris@16 280 m_redoStack.pop();
Chris@16 281 // no need to clip
Chris@16 282
Chris@16 283 updateActions();
Chris@41 284
Chris@41 285 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@16 286 }
Chris@16 287
Chris@16 288 void
Chris@17 289 CommandHistory::setUndoLimit(int limit)
Chris@16 290 {
Chris@16 291 if (limit > 0 && limit != m_undoLimit) {
Chris@16 292 m_undoLimit = limit;
Chris@16 293 clipCommands();
Chris@16 294 }
Chris@16 295 }
Chris@16 296
Chris@16 297 void
Chris@17 298 CommandHistory::setRedoLimit(int limit)
Chris@16 299 {
Chris@16 300 if (limit > 0 && limit != m_redoLimit) {
Chris@16 301 m_redoLimit = limit;
Chris@16 302 clipCommands();
Chris@16 303 }
Chris@16 304 }
Chris@16 305
Chris@16 306 void
Chris@46 307 CommandHistory::setMenuLimit(int limit)
Chris@46 308 {
Chris@46 309 m_menuLimit = limit;
Chris@46 310 updateActions();
Chris@46 311 }
Chris@46 312
Chris@46 313 void
Chris@47 314 CommandHistory::setBundleTimeout(int ms)
Chris@47 315 {
Chris@47 316 m_bundleTimeout = ms;
Chris@47 317 }
Chris@47 318
Chris@47 319 void
Chris@17 320 CommandHistory::documentSaved()
Chris@16 321 {
Chris@47 322 closeBundle();
Chris@16 323 m_savedAt = m_undoStack.size();
Chris@16 324 }
Chris@16 325
Chris@16 326 void
Chris@17 327 CommandHistory::clipCommands()
Chris@16 328 {
Chris@16 329 if ((int)m_undoStack.size() > m_undoLimit) {
Chris@16 330 m_savedAt -= (m_undoStack.size() - m_undoLimit);
Chris@16 331 }
Chris@16 332
Chris@16 333 clipStack(m_undoStack, m_undoLimit);
Chris@16 334 clipStack(m_redoStack, m_redoLimit);
Chris@16 335 }
Chris@16 336
Chris@16 337 void
Chris@17 338 CommandHistory::clipStack(CommandStack &stack, int limit)
Chris@16 339 {
Chris@16 340 int i;
Chris@16 341
Chris@16 342 if ((int)stack.size() > limit) {
Chris@16 343
Chris@16 344 CommandStack tempStack;
Chris@16 345
Chris@16 346 for (i = 0; i < limit; ++i) {
Chris@16 347 Command *command = stack.top();
Chris@47 348 std::cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
Chris@16 349 tempStack.push(stack.top());
Chris@16 350 stack.pop();
Chris@16 351 }
Chris@16 352
Chris@16 353 clearStack(stack);
Chris@16 354
Chris@16 355 for (i = 0; i < m_undoLimit; ++i) {
Chris@16 356 stack.push(tempStack.top());
Chris@16 357 tempStack.pop();
Chris@16 358 }
Chris@16 359 }
Chris@16 360 }
Chris@16 361
Chris@16 362 void
Chris@17 363 CommandHistory::clearStack(CommandStack &stack)
Chris@16 364 {
Chris@16 365 while (!stack.empty()) {
Chris@16 366 Command *command = stack.top();
Chris@46 367 // Not safe to call getName() on a command about to be deleted
Chris@47 368 std::cerr << "CommandHistory::clearStack: About to delete command " << command << std::endl;
Chris@16 369 delete command;
Chris@16 370 stack.pop();
Chris@16 371 }
Chris@16 372 }
Chris@16 373
Chris@16 374 void
Chris@17 375 CommandHistory::undoActivated(QAction *action)
Chris@16 376 {
Chris@16 377 int pos = m_actionCounts[action];
Chris@16 378 for (int i = 0; i <= pos; ++i) {
Chris@16 379 undo();
Chris@16 380 }
Chris@16 381 }
Chris@16 382
Chris@16 383 void
Chris@17 384 CommandHistory::redoActivated(QAction *action)
Chris@16 385 {
Chris@16 386 int pos = m_actionCounts[action];
Chris@16 387 for (int i = 0; i <= pos; ++i) {
Chris@16 388 redo();
Chris@16 389 }
Chris@16 390 }
Chris@16 391
Chris@16 392 void
Chris@17 393 CommandHistory::updateActions()
Chris@16 394 {
Chris@16 395 m_actionCounts.clear();
Chris@16 396
Chris@16 397 for (int undo = 0; undo <= 1; ++undo) {
Chris@16 398
Chris@17 399 QAction *action(undo ? m_undoAction : m_redoAction);
Chris@17 400 QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction);
Chris@16 401 QMenu *menu(undo ? m_undoMenu : m_redoMenu);
Chris@16 402 CommandStack &stack(undo ? m_undoStack : m_redoStack);
Chris@16 403
Chris@17 404 if (stack.empty()) {
Chris@17 405
Chris@17 406 QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo"));
Chris@17 407
Chris@17 408 action->setEnabled(false);
Chris@17 409 action->setText(text);
Chris@17 410
Chris@17 411 menuAction->setEnabled(false);
Chris@17 412 menuAction->setText(text);
Chris@17 413
Chris@17 414 } else {
Chris@17 415
Chris@17 416 action->setEnabled(true);
Chris@17 417 menuAction->setEnabled(true);
Chris@17 418
Chris@17 419 QString commandName = stack.top()->getName();
Chris@17 420 commandName.replace(QRegExp("&"), "");
Chris@17 421
Chris@17 422 QString text = (undo ? tr("&Undo %1") : tr("Re&do %1"))
Chris@17 423 .arg(commandName);
Chris@17 424
Chris@17 425 action->setText(text);
Chris@17 426 menuAction->setText(text);
Chris@17 427 }
Chris@17 428
Chris@16 429 menu->clear();
Chris@16 430
Chris@16 431 CommandStack tempStack;
Chris@16 432 int j = 0;
Chris@16 433
Chris@46 434 while (j < m_menuLimit && !stack.empty()) {
Chris@16 435
Chris@16 436 Command *command = stack.top();
Chris@16 437 tempStack.push(command);
Chris@16 438 stack.pop();
Chris@16 439
Chris@17 440 QString commandName = command->getName();
Chris@16 441 commandName.replace(QRegExp("&"), "");
Chris@16 442
Chris@16 443 QString text;
Chris@16 444 if (undo) text = tr("&Undo %1").arg(commandName);
Chris@16 445 else text = tr("Re&do %1").arg(commandName);
Chris@16 446
Chris@16 447 QAction *action = menu->addAction(text);
Chris@16 448 m_actionCounts[action] = j++;
Chris@16 449 }
Chris@16 450
Chris@16 451 while (!tempStack.empty()) {
Chris@16 452 stack.push(tempStack.top());
Chris@16 453 tempStack.pop();
Chris@16 454 }
Chris@16 455 }
Chris@16 456 }
Chris@16 457