annotate base/CommandHistory.cpp @ 52:d397ea0a79f5

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