annotate widgets/CommandHistory.cpp @ 378:22b72f0f6a4e

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