annotate base/CommandHistory.cpp @ 117:c30728d5625c sv1-v0.9rc1

* Make vertical scale alignment modes work in note layer as well as time-value layer, and several significant fixes to it * Make it possible to draw notes properly on the note layer * Show units (and frequencies etc in note layer's case) in the time-value and note layer description boxes * Minor fix to item edit dialog layout * Some minor menu rearrangement * Comment out a lot of debug output * Add SV website and reference URLs to Help menu, and add code to (attempt to) open them in the user's preferred browser
author Chris Cannam
date Fri, 12 May 2006 14:40:43 +0000
parents 90ade4fa63be
children ce6f65ab3327
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@117 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