annotate widgets/CommandHistory.cpp @ 454:e2a40fdadd8c

Various fixes: * Fix handling of HTTP redirects (avoiding crashes... I hope) * Fix failure to delete FFT models when a feature extraction model transformer was abandoned (also a cause of crashes in the past) * Fix deadlock when said transform was abandoned before its source model was ready because the session was being cleared (and so the source model would never be ready)
author Chris Cannam
date Fri, 28 Nov 2008 13:36:13 +0000
parents 80e279e4f9fe
children 73a58a4dfebd
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@398 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@397 142 #ifdef DEBUG_COMMAND_HISTORY
Chris@397 143 std::cerr << "CommandHistory::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << ": execute = " << execute << ", bundle = " << bundle << " (m_currentCompound = " << m_currentCompound << ", m_currentBundle = " << m_currentBundle << ")" << std::endl;
Chris@397 144 #endif
Chris@397 145
Chris@376 146 if (m_currentCompound) {
Chris@376 147 addToCompound(command, execute);
Chris@376 148 return;
Chris@376 149 }
Chris@376 150
Chris@376 151 if (bundle) {
Chris@376 152 addToBundle(command, execute);
Chris@376 153 return;
Chris@376 154 } else if (m_currentBundle) {
Chris@376 155 closeBundle();
Chris@376 156 }
Chris@376 157
Chris@377 158 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 159 if (!m_redoStack.empty()) {
Chris@377 160 std::cerr << "CommandHistory::clearing redo stack" << std::endl;
Chris@377 161 }
Chris@377 162 #endif
Chris@376 163
Chris@376 164 // We can't redo after adding a command
Chris@376 165 clearStack(m_redoStack);
Chris@376 166
Chris@376 167 // can we reach savedAt?
Chris@376 168 if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope
Chris@376 169
Chris@376 170 m_undoStack.push(command);
Chris@376 171 clipCommands();
Chris@376 172
Chris@376 173 if (execute) {
Chris@376 174 command->execute();
Chris@376 175 }
Chris@376 176
Chris@376 177 // Emit even if we aren't executing the command, because
Chris@376 178 // someone must have executed it for this to make any sense
Chris@376 179 emit commandExecuted();
Chris@376 180 emit commandExecuted(command);
Chris@376 181
Chris@376 182 updateActions();
Chris@376 183 }
Chris@376 184
Chris@376 185 void
Chris@376 186 CommandHistory::addToBundle(Command *command, bool execute)
Chris@376 187 {
Chris@376 188 if (m_currentBundle) {
Chris@376 189 if (!command || (command->getName() != m_currentBundleName)) {
Chris@377 190 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 191 std::cerr << "CommandHistory::addToBundle: "
Chris@377 192 << command->getName().toStdString()
Chris@377 193 << ": closing current bundle" << std::endl;
Chris@377 194 #endif
Chris@376 195 closeBundle();
Chris@376 196 }
Chris@376 197 }
Chris@376 198
Chris@376 199 if (!command) return;
Chris@376 200
Chris@376 201 if (!m_currentBundle) {
Chris@377 202
Chris@377 203 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 204 std::cerr << "CommandHistory::addToBundle: "
Chris@377 205 << command->getName().toStdString()
Chris@377 206 << ": creating new bundle" << std::endl;
Chris@377 207 #endif
Chris@377 208
Chris@376 209 // need to addCommand before setting m_currentBundle, as addCommand
Chris@376 210 // with bundle false will reset m_currentBundle to 0
Chris@397 211 MacroCommand *mc = new BundleCommand(command->getName());
Chris@376 212 addCommand(mc, false);
Chris@376 213 m_currentBundle = mc;
Chris@376 214 m_currentBundleName = command->getName();
Chris@376 215 }
Chris@376 216
Chris@377 217 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 218 std::cerr << "CommandHistory::addToBundle: "
Chris@377 219 << command->getName().toStdString()
Chris@377 220 << ": adding to bundle" << std::endl;
Chris@377 221 #endif
Chris@377 222
Chris@376 223 if (execute) command->execute();
Chris@376 224 m_currentBundle->addCommand(command);
Chris@376 225
Chris@377 226 // Emit even if we aren't executing the command, because
Chris@377 227 // someone must have executed it for this to make any sense
Chris@377 228 emit commandExecuted();
Chris@377 229 emit commandExecuted(command);
Chris@377 230
Chris@377 231 updateActions();
Chris@377 232
Chris@376 233 delete m_bundleTimer;
Chris@376 234 m_bundleTimer = new QTimer(this);
Chris@376 235 connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout()));
Chris@376 236 m_bundleTimer->start(m_bundleTimeout);
Chris@376 237 }
Chris@376 238
Chris@376 239 void
Chris@376 240 CommandHistory::closeBundle()
Chris@376 241 {
Chris@377 242 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 243 std::cerr << "CommandHistory::closeBundle" << std::endl;
Chris@377 244 #endif
Chris@377 245
Chris@376 246 m_currentBundle = 0;
Chris@376 247 m_currentBundleName = "";
Chris@376 248 }
Chris@376 249
Chris@376 250 void
Chris@376 251 CommandHistory::bundleTimerTimeout()
Chris@376 252 {
Chris@377 253 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 254 std::cerr << "CommandHistory::bundleTimerTimeout: bundle is " << m_currentBundle << std::endl;
Chris@377 255 #endif
Chris@377 256
Chris@376 257 closeBundle();
Chris@376 258 }
Chris@376 259
Chris@376 260 void
Chris@376 261 CommandHistory::addToCompound(Command *command, bool execute)
Chris@376 262 {
Chris@377 263 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 264 std::cerr << "CommandHistory::addToCompound: " << command->getName().toLocal8Bit().data() << std::endl;
Chris@377 265 #endif
Chris@376 266 if (!m_currentCompound) {
Chris@376 267 std::cerr << "CommandHistory::addToCompound: ERROR: no compound operation in progress!" << std::endl;
Chris@376 268 return;
Chris@376 269 }
Chris@376 270
Chris@376 271 if (execute) command->execute();
Chris@376 272 m_currentCompound->addCommand(command);
Chris@376 273 }
Chris@376 274
Chris@376 275 void
Chris@376 276 CommandHistory::startCompoundOperation(QString name, bool execute)
Chris@376 277 {
Chris@376 278 if (m_currentCompound) {
Chris@376 279 std::cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl;
Chris@376 280 std::cerr << "(name is " << m_currentCompound->getName().toLocal8Bit().data() << ")" << std::endl;
Chris@376 281 return;
Chris@376 282 }
Chris@376 283
Chris@376 284 closeBundle();
Chris@376 285
Chris@376 286 m_currentCompound = new MacroCommand(name);
Chris@376 287 m_executeCompound = execute;
Chris@376 288 }
Chris@376 289
Chris@376 290 void
Chris@376 291 CommandHistory::endCompoundOperation()
Chris@376 292 {
Chris@376 293 if (!m_currentCompound) {
Chris@376 294 std::cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl;
Chris@376 295 return;
Chris@376 296 }
Chris@376 297
Chris@376 298 MacroCommand *toAdd = m_currentCompound;
Chris@376 299 m_currentCompound = 0;
Chris@376 300
Chris@376 301 if (toAdd->haveCommands()) {
Chris@376 302
Chris@376 303 // We don't execute the macro command here, because we have
Chris@376 304 // been executing the individual commands as we went along if
Chris@376 305 // m_executeCompound was true.
Chris@376 306 addCommand(toAdd, false);
Chris@376 307 }
Chris@376 308 }
Chris@376 309
Chris@376 310 void
Chris@376 311 CommandHistory::addExecutedCommand(Command *command)
Chris@376 312 {
Chris@376 313 addCommand(command, false);
Chris@376 314 }
Chris@376 315
Chris@376 316 void
Chris@376 317 CommandHistory::addCommandAndExecute(Command *command)
Chris@376 318 {
Chris@376 319 addCommand(command, true);
Chris@376 320 }
Chris@376 321
Chris@376 322 void
Chris@376 323 CommandHistory::undo()
Chris@376 324 {
Chris@376 325 if (m_undoStack.empty()) return;
Chris@376 326
Chris@377 327 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 328 std::cerr << "CommandHistory::undo()" << std::endl;
Chris@377 329 #endif
Chris@377 330
Chris@376 331 closeBundle();
Chris@376 332
Chris@376 333 Command *command = m_undoStack.top();
Chris@376 334 command->unexecute();
Chris@376 335 emit commandExecuted();
Chris@376 336 emit commandUnexecuted(command);
Chris@376 337
Chris@376 338 m_redoStack.push(command);
Chris@376 339 m_undoStack.pop();
Chris@376 340
Chris@376 341 clipCommands();
Chris@376 342 updateActions();
Chris@376 343
Chris@376 344 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@376 345 }
Chris@376 346
Chris@376 347 void
Chris@376 348 CommandHistory::redo()
Chris@376 349 {
Chris@376 350 if (m_redoStack.empty()) return;
Chris@376 351
Chris@377 352 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 353 std::cerr << "CommandHistory::redo()" << std::endl;
Chris@377 354 #endif
Chris@377 355
Chris@376 356 closeBundle();
Chris@376 357
Chris@376 358 Command *command = m_redoStack.top();
Chris@376 359 command->execute();
Chris@376 360 emit commandExecuted();
Chris@376 361 emit commandExecuted(command);
Chris@376 362
Chris@376 363 m_undoStack.push(command);
Chris@376 364 m_redoStack.pop();
Chris@376 365 // no need to clip
Chris@376 366
Chris@376 367 updateActions();
Chris@376 368
Chris@376 369 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@376 370 }
Chris@376 371
Chris@376 372 void
Chris@376 373 CommandHistory::setUndoLimit(int limit)
Chris@376 374 {
Chris@376 375 if (limit > 0 && limit != m_undoLimit) {
Chris@376 376 m_undoLimit = limit;
Chris@376 377 clipCommands();
Chris@376 378 }
Chris@376 379 }
Chris@376 380
Chris@376 381 void
Chris@376 382 CommandHistory::setRedoLimit(int limit)
Chris@376 383 {
Chris@376 384 if (limit > 0 && limit != m_redoLimit) {
Chris@376 385 m_redoLimit = limit;
Chris@376 386 clipCommands();
Chris@376 387 }
Chris@376 388 }
Chris@376 389
Chris@376 390 void
Chris@376 391 CommandHistory::setMenuLimit(int limit)
Chris@376 392 {
Chris@376 393 m_menuLimit = limit;
Chris@376 394 updateActions();
Chris@376 395 }
Chris@376 396
Chris@376 397 void
Chris@376 398 CommandHistory::setBundleTimeout(int ms)
Chris@376 399 {
Chris@376 400 m_bundleTimeout = ms;
Chris@376 401 }
Chris@376 402
Chris@376 403 void
Chris@376 404 CommandHistory::documentSaved()
Chris@376 405 {
Chris@376 406 closeBundle();
Chris@376 407 m_savedAt = m_undoStack.size();
Chris@376 408 }
Chris@376 409
Chris@376 410 void
Chris@376 411 CommandHistory::clipCommands()
Chris@376 412 {
Chris@376 413 if ((int)m_undoStack.size() > m_undoLimit) {
Chris@376 414 m_savedAt -= (m_undoStack.size() - m_undoLimit);
Chris@376 415 }
Chris@376 416
Chris@376 417 clipStack(m_undoStack, m_undoLimit);
Chris@376 418 clipStack(m_redoStack, m_redoLimit);
Chris@376 419 }
Chris@376 420
Chris@376 421 void
Chris@376 422 CommandHistory::clipStack(CommandStack &stack, int limit)
Chris@376 423 {
Chris@376 424 int i;
Chris@376 425
Chris@376 426 if ((int)stack.size() > limit) {
Chris@376 427
Chris@376 428 CommandStack tempStack;
Chris@376 429
Chris@376 430 for (i = 0; i < limit; ++i) {
Chris@377 431 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 432 Command *command = stack.top();
Chris@377 433 std::cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
Chris@377 434 #endif
Chris@376 435 tempStack.push(stack.top());
Chris@376 436 stack.pop();
Chris@376 437 }
Chris@376 438
Chris@376 439 clearStack(stack);
Chris@376 440
Chris@376 441 for (i = 0; i < m_undoLimit; ++i) {
Chris@376 442 stack.push(tempStack.top());
Chris@376 443 tempStack.pop();
Chris@376 444 }
Chris@376 445 }
Chris@376 446 }
Chris@376 447
Chris@376 448 void
Chris@376 449 CommandHistory::clearStack(CommandStack &stack)
Chris@376 450 {
Chris@376 451 while (!stack.empty()) {
Chris@376 452 Command *command = stack.top();
Chris@376 453 // Not safe to call getName() on a command about to be deleted
Chris@377 454 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 455 std::cerr << "CommandHistory::clearStack: About to delete command " << command << std::endl;
Chris@377 456 #endif
Chris@376 457 delete command;
Chris@376 458 stack.pop();
Chris@376 459 }
Chris@376 460 }
Chris@376 461
Chris@376 462 void
Chris@376 463 CommandHistory::undoActivated(QAction *action)
Chris@376 464 {
Chris@376 465 int pos = m_actionCounts[action];
Chris@376 466 for (int i = 0; i <= pos; ++i) {
Chris@376 467 undo();
Chris@376 468 }
Chris@376 469 }
Chris@376 470
Chris@376 471 void
Chris@376 472 CommandHistory::redoActivated(QAction *action)
Chris@376 473 {
Chris@376 474 int pos = m_actionCounts[action];
Chris@376 475 for (int i = 0; i <= pos; ++i) {
Chris@376 476 redo();
Chris@376 477 }
Chris@376 478 }
Chris@376 479
Chris@376 480 void
Chris@376 481 CommandHistory::updateActions()
Chris@376 482 {
Chris@376 483 m_actionCounts.clear();
Chris@376 484
Chris@376 485 for (int undo = 0; undo <= 1; ++undo) {
Chris@376 486
Chris@376 487 QAction *action(undo ? m_undoAction : m_redoAction);
Chris@376 488 QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction);
Chris@376 489 QMenu *menu(undo ? m_undoMenu : m_redoMenu);
Chris@376 490 CommandStack &stack(undo ? m_undoStack : m_redoStack);
Chris@376 491
Chris@376 492 if (stack.empty()) {
Chris@376 493
Chris@376 494 QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo"));
Chris@376 495
Chris@376 496 action->setEnabled(false);
Chris@376 497 action->setText(text);
Chris@376 498
Chris@376 499 menuAction->setEnabled(false);
Chris@376 500 menuAction->setText(text);
Chris@376 501
Chris@376 502 } else {
Chris@376 503
Chris@376 504 action->setEnabled(true);
Chris@376 505 menuAction->setEnabled(true);
Chris@376 506
Chris@376 507 QString commandName = stack.top()->getName();
Chris@376 508 commandName.replace(QRegExp("&"), "");
Chris@376 509
Chris@376 510 QString text = (undo ? tr("&Undo %1") : tr("Re&do %1"))
Chris@376 511 .arg(commandName);
Chris@376 512
Chris@376 513 action->setText(text);
Chris@376 514 menuAction->setText(text);
Chris@376 515 }
Chris@376 516
Chris@376 517 menu->clear();
Chris@376 518
Chris@376 519 CommandStack tempStack;
Chris@376 520 int j = 0;
Chris@376 521
Chris@376 522 while (j < m_menuLimit && !stack.empty()) {
Chris@376 523
Chris@376 524 Command *command = stack.top();
Chris@376 525 tempStack.push(command);
Chris@376 526 stack.pop();
Chris@376 527
Chris@376 528 QString commandName = command->getName();
Chris@376 529 commandName.replace(QRegExp("&"), "");
Chris@376 530
Chris@376 531 QString text;
Chris@376 532 if (undo) text = tr("&Undo %1").arg(commandName);
Chris@376 533 else text = tr("Re&do %1").arg(commandName);
Chris@376 534
Chris@376 535 QAction *action = menu->addAction(text);
Chris@376 536 m_actionCounts[action] = j++;
Chris@376 537 }
Chris@376 538
Chris@376 539 while (!tempStack.empty()) {
Chris@376 540 stack.push(tempStack.top());
Chris@376 541 tempStack.pop();
Chris@376 542 }
Chris@376 543 }
Chris@376 544 }
Chris@376 545