annotate base/CommandHistory.cpp @ 115:90ade4fa63be

* Fix serious failure to reload "imported" (i.e. all non-derived non-main) models from .sv file * Give a short playback duration to notes with formal duration of 0 or 1 * Show crosshairs on spectrogram even when there is another layer on top (if it isn't opaque) * Always paste to the same time in the layer as the cut/copy was from, rather than to the playback pointer -- less flexible, but more predictable and less annoying. We probably need a way to get the old behaviour if pasting from somewhere else in the future (e.g. from a text file), but we can't do that yet anyway * Use a compound operation for dragging and resizing selections, so as to ensure a single undo operation works * Use a note model as the target for feature extraction plugins that output variable samplerate data with more than one value per feature * Avoid possible crashes in cut/paste if a layer proves to have no model
author Chris Cannam
date Thu, 11 May 2006 11:35:46 +0000
parents c983dda79f72
children c30728d5625c
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@78 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@115 118 CommandHistory::addCommand(Command *command)
Chris@115 119 {
Chris@115 120 if (!command) return;
Chris@115 121
Chris@115 122 if (m_currentCompound) {
Chris@115 123 addToCompound(command, m_executeCompound);
Chris@115 124 return;
Chris@115 125 }
Chris@115 126
Chris@115 127 addCommand(command, true);
Chris@115 128 }
Chris@115 129
Chris@115 130 void
Chris@47 131 CommandHistory::addCommand(Command *command, bool execute, bool bundle)
Chris@16 132 {
Chris@16 133 if (!command) return;
Chris@16 134
Chris@47 135 if (m_currentCompound) {
Chris@115 136 addToCompound(command, execute);
Chris@44 137 return;
Chris@44 138 }
Chris@44 139
Chris@47 140 if (bundle) {
Chris@47 141 addToBundle(command, execute);
Chris@47 142 return;
Chris@47 143 } else if (m_currentBundle) {
Chris@47 144 closeBundle();
Chris@47 145 }
Chris@47 146
Chris@78 147 // std::cerr << "CommandHistory::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
Chris@16 148
Chris@16 149 // We can't redo after adding a command
Chris@78 150 // std::cerr << "CommandHistory::clearing redo stack" << std::endl;
Chris@16 151 clearStack(m_redoStack);
Chris@16 152
Chris@16 153 // can we reach savedAt?
Chris@16 154 if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope
Chris@16 155
Chris@16 156 m_undoStack.push(command);
Chris@16 157 clipCommands();
Chris@16 158
Chris@16 159 if (execute) {
Chris@16 160 command->execute();
Chris@16 161 }
Chris@16 162
Chris@17 163 // Emit even if we aren't executing the command, because
Chris@17 164 // someone must have executed it for this to make any sense
Chris@17 165 emit commandExecuted();
Chris@17 166 emit commandExecuted(command);
Chris@17 167
Chris@16 168 updateActions();
Chris@16 169 }
Chris@16 170
Chris@16 171 void
Chris@47 172 CommandHistory::addToBundle(Command *command, bool execute)
Chris@44 173 {
Chris@47 174 if (m_currentBundle) {
Chris@47 175 if (!command || (command->getName() != m_currentBundleName)) {
Chris@47 176 closeBundle();
Chris@47 177 }
Chris@47 178 }
Chris@44 179
Chris@47 180 if (!command) return;
Chris@47 181
Chris@47 182 if (!m_currentBundle) {
Chris@47 183 // need to addCommand before setting m_currentBundle, as addCommand
Chris@47 184 // with bundle false will reset m_currentBundle to 0
Chris@47 185 MacroCommand *mc = new MacroCommand(command->getName());
Chris@47 186 addCommand(mc, false);
Chris@47 187 m_currentBundle = mc;
Chris@47 188 m_currentBundleName = command->getName();
Chris@47 189 }
Chris@47 190
Chris@47 191 if (execute) command->execute();
Chris@47 192 m_currentBundle->addCommand(command);
Chris@47 193
Chris@47 194 delete m_bundleTimer;
Chris@47 195 m_bundleTimer = new QTimer(this);
Chris@47 196 connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout()));
Chris@47 197 m_bundleTimer->start(m_bundleTimeout);
Chris@47 198 }
Chris@47 199
Chris@47 200 void
Chris@47 201 CommandHistory::closeBundle()
Chris@47 202 {
Chris@47 203 m_currentBundle = 0;
Chris@47 204 m_currentBundleName = "";
Chris@47 205 }
Chris@47 206
Chris@47 207 void
Chris@47 208 CommandHistory::bundleTimerTimeout()
Chris@47 209 {
Chris@47 210 closeBundle();
Chris@47 211 }
Chris@47 212
Chris@47 213 void
Chris@115 214 CommandHistory::addToCompound(Command *command, bool execute)
Chris@47 215 {
Chris@78 216 // std::cerr << "CommandHistory::addToCompound: " << command->getName().toLocal8Bit().data() << std::endl;
Chris@47 217
Chris@115 218 if (execute) command->execute();
Chris@47 219 m_currentCompound->addCommand(command);
Chris@44 220 }
Chris@44 221
Chris@44 222 void
Chris@44 223 CommandHistory::startCompoundOperation(QString name, bool execute)
Chris@44 224 {
Chris@47 225 if (m_currentCompound) {
Chris@47 226 std::cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl;
Chris@47 227 std::cerr << "(name is " << m_currentCompound->getName().toLocal8Bit().data() << ")" << std::endl;
Chris@44 228 }
Chris@47 229
Chris@47 230 closeBundle();
Chris@47 231
Chris@47 232 m_currentCompound = new MacroCommand(name);
Chris@47 233 m_executeCompound = execute;
Chris@44 234 }
Chris@44 235
Chris@44 236 void
Chris@44 237 CommandHistory::endCompoundOperation()
Chris@44 238 {
Chris@47 239 if (!m_currentCompound) {
Chris@47 240 std::cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl;
Chris@44 241 }
Chris@44 242
Chris@53 243 MacroCommand *toAdd = m_currentCompound;
Chris@47 244 m_currentCompound = 0;
Chris@44 245
Chris@53 246 if (toAdd->haveCommands()) {
Chris@53 247
Chris@53 248 // We don't execute the macro command here, because we have
Chris@53 249 // been executing the individual commands as we went along if
Chris@53 250 // m_executeCompound was true.
Chris@53 251 addCommand(toAdd, false);
Chris@53 252 }
Chris@44 253 }
Chris@44 254
Chris@44 255 void
Chris@17 256 CommandHistory::addExecutedCommand(Command *command)
Chris@17 257 {
Chris@17 258 addCommand(command, false);
Chris@17 259 }
Chris@17 260
Chris@17 261 void
Chris@17 262 CommandHistory::addCommandAndExecute(Command *command)
Chris@17 263 {
Chris@17 264 addCommand(command, true);
Chris@17 265 }
Chris@17 266
Chris@17 267 void
Chris@17 268 CommandHistory::undo()
Chris@16 269 {
Chris@16 270 if (m_undoStack.empty()) return;
Chris@16 271
Chris@47 272 closeBundle();
Chris@47 273
Chris@16 274 Command *command = m_undoStack.top();
Chris@16 275 command->unexecute();
Chris@16 276 emit commandExecuted();
Chris@17 277 emit commandUnexecuted(command);
Chris@16 278
Chris@16 279 m_redoStack.push(command);
Chris@16 280 m_undoStack.pop();
Chris@16 281
Chris@16 282 clipCommands();
Chris@16 283 updateActions();
Chris@16 284
Chris@16 285 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@16 286 }
Chris@16 287
Chris@16 288 void
Chris@17 289 CommandHistory::redo()
Chris@16 290 {
Chris@16 291 if (m_redoStack.empty()) return;
Chris@16 292
Chris@47 293 closeBundle();
Chris@47 294
Chris@16 295 Command *command = m_redoStack.top();
Chris@16 296 command->execute();
Chris@16 297 emit commandExecuted();
Chris@16 298 emit commandExecuted(command);
Chris@16 299
Chris@16 300 m_undoStack.push(command);
Chris@16 301 m_redoStack.pop();
Chris@16 302 // no need to clip
Chris@16 303
Chris@16 304 updateActions();
Chris@41 305
Chris@41 306 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@16 307 }
Chris@16 308
Chris@16 309 void
Chris@17 310 CommandHistory::setUndoLimit(int limit)
Chris@16 311 {
Chris@16 312 if (limit > 0 && limit != m_undoLimit) {
Chris@16 313 m_undoLimit = limit;
Chris@16 314 clipCommands();
Chris@16 315 }
Chris@16 316 }
Chris@16 317
Chris@16 318 void
Chris@17 319 CommandHistory::setRedoLimit(int limit)
Chris@16 320 {
Chris@16 321 if (limit > 0 && limit != m_redoLimit) {
Chris@16 322 m_redoLimit = limit;
Chris@16 323 clipCommands();
Chris@16 324 }
Chris@16 325 }
Chris@16 326
Chris@16 327 void
Chris@46 328 CommandHistory::setMenuLimit(int limit)
Chris@46 329 {
Chris@46 330 m_menuLimit = limit;
Chris@46 331 updateActions();
Chris@46 332 }
Chris@46 333
Chris@46 334 void
Chris@47 335 CommandHistory::setBundleTimeout(int ms)
Chris@47 336 {
Chris@47 337 m_bundleTimeout = ms;
Chris@47 338 }
Chris@47 339
Chris@47 340 void
Chris@17 341 CommandHistory::documentSaved()
Chris@16 342 {
Chris@47 343 closeBundle();
Chris@16 344 m_savedAt = m_undoStack.size();
Chris@16 345 }
Chris@16 346
Chris@16 347 void
Chris@17 348 CommandHistory::clipCommands()
Chris@16 349 {
Chris@16 350 if ((int)m_undoStack.size() > m_undoLimit) {
Chris@16 351 m_savedAt -= (m_undoStack.size() - m_undoLimit);
Chris@16 352 }
Chris@16 353
Chris@16 354 clipStack(m_undoStack, m_undoLimit);
Chris@16 355 clipStack(m_redoStack, m_redoLimit);
Chris@16 356 }
Chris@16 357
Chris@16 358 void
Chris@17 359 CommandHistory::clipStack(CommandStack &stack, int limit)
Chris@16 360 {
Chris@16 361 int i;
Chris@16 362
Chris@16 363 if ((int)stack.size() > limit) {
Chris@16 364
Chris@16 365 CommandStack tempStack;
Chris@16 366
Chris@16 367 for (i = 0; i < limit; ++i) {
Chris@16 368 Command *command = stack.top();
Chris@78 369 // std::cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
Chris@16 370 tempStack.push(stack.top());
Chris@16 371 stack.pop();
Chris@16 372 }
Chris@16 373
Chris@16 374 clearStack(stack);
Chris@16 375
Chris@16 376 for (i = 0; i < m_undoLimit; ++i) {
Chris@16 377 stack.push(tempStack.top());
Chris@16 378 tempStack.pop();
Chris@16 379 }
Chris@16 380 }
Chris@16 381 }
Chris@16 382
Chris@16 383 void
Chris@17 384 CommandHistory::clearStack(CommandStack &stack)
Chris@16 385 {
Chris@16 386 while (!stack.empty()) {
Chris@16 387 Command *command = stack.top();
Chris@46 388 // Not safe to call getName() on a command about to be deleted
Chris@47 389 std::cerr << "CommandHistory::clearStack: About to delete command " << command << std::endl;
Chris@16 390 delete command;
Chris@16 391 stack.pop();
Chris@16 392 }
Chris@16 393 }
Chris@16 394
Chris@16 395 void
Chris@17 396 CommandHistory::undoActivated(QAction *action)
Chris@16 397 {
Chris@16 398 int pos = m_actionCounts[action];
Chris@16 399 for (int i = 0; i <= pos; ++i) {
Chris@16 400 undo();
Chris@16 401 }
Chris@16 402 }
Chris@16 403
Chris@16 404 void
Chris@17 405 CommandHistory::redoActivated(QAction *action)
Chris@16 406 {
Chris@16 407 int pos = m_actionCounts[action];
Chris@16 408 for (int i = 0; i <= pos; ++i) {
Chris@16 409 redo();
Chris@16 410 }
Chris@16 411 }
Chris@16 412
Chris@16 413 void
Chris@17 414 CommandHistory::updateActions()
Chris@16 415 {
Chris@16 416 m_actionCounts.clear();
Chris@16 417
Chris@16 418 for (int undo = 0; undo <= 1; ++undo) {
Chris@16 419
Chris@17 420 QAction *action(undo ? m_undoAction : m_redoAction);
Chris@17 421 QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction);
Chris@16 422 QMenu *menu(undo ? m_undoMenu : m_redoMenu);
Chris@16 423 CommandStack &stack(undo ? m_undoStack : m_redoStack);
Chris@16 424
Chris@17 425 if (stack.empty()) {
Chris@17 426
Chris@17 427 QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo"));
Chris@17 428
Chris@17 429 action->setEnabled(false);
Chris@17 430 action->setText(text);
Chris@17 431
Chris@17 432 menuAction->setEnabled(false);
Chris@17 433 menuAction->setText(text);
Chris@17 434
Chris@17 435 } else {
Chris@17 436
Chris@17 437 action->setEnabled(true);
Chris@17 438 menuAction->setEnabled(true);
Chris@17 439
Chris@17 440 QString commandName = stack.top()->getName();
Chris@17 441 commandName.replace(QRegExp("&"), "");
Chris@17 442
Chris@17 443 QString text = (undo ? tr("&Undo %1") : tr("Re&do %1"))
Chris@17 444 .arg(commandName);
Chris@17 445
Chris@17 446 action->setText(text);
Chris@17 447 menuAction->setText(text);
Chris@17 448 }
Chris@17 449
Chris@16 450 menu->clear();
Chris@16 451
Chris@16 452 CommandStack tempStack;
Chris@16 453 int j = 0;
Chris@16 454
Chris@46 455 while (j < m_menuLimit && !stack.empty()) {
Chris@16 456
Chris@16 457 Command *command = stack.top();
Chris@16 458 tempStack.push(command);
Chris@16 459 stack.pop();
Chris@16 460
Chris@17 461 QString commandName = command->getName();
Chris@16 462 commandName.replace(QRegExp("&"), "");
Chris@16 463
Chris@16 464 QString text;
Chris@16 465 if (undo) text = tr("&Undo %1").arg(commandName);
Chris@16 466 else text = tr("Re&do %1").arg(commandName);
Chris@16 467
Chris@16 468 QAction *action = menu->addAction(text);
Chris@16 469 m_actionCounts[action] = j++;
Chris@16 470 }
Chris@16 471
Chris@16 472 while (!tempStack.empty()) {
Chris@16 473 stack.push(tempStack.top());
Chris@16 474 tempStack.pop();
Chris@16 475 }
Chris@16 476 }
Chris@16 477 }
Chris@16 478