annotate base/CommandHistory.cpp @ 97:22494cc28c9f

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