annotate base/CommandHistory.cpp @ 316:3a6725f285d6

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