svgui
1.9
|
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 00002 00003 /* 00004 Sonic Visualiser 00005 An audio file viewer and annotation editor. 00006 Centre for Digital Music, Queen Mary, University of London. 00007 00008 This program is free software; you can redistribute it and/or 00009 modify it under the terms of the GNU General Public License as 00010 published by the Free Software Foundation; either version 2 of the 00011 License, or (at your option) any later version. See the file 00012 COPYING included with this distribution for more information. 00013 */ 00014 00015 /* 00016 This is a modified version of a source file from the Rosegarden 00017 MIDI and audio sequencer and notation editor, copyright 2000-2006 00018 Chris Cannam, distributed under the GNU General Public License. 00019 00020 This file contains traces of the KCommandHistory class from the KDE 00021 project, copyright 2000 Werner Trobin and David Faure and 00022 distributed under the GNU Lesser General Public License. 00023 */ 00024 00025 #include "CommandHistory.h" 00026 00027 #include "base/Command.h" 00028 00029 #include <QRegExp> 00030 #include <QMenu> 00031 #include <QToolBar> 00032 #include <QString> 00033 #include <QTimer> 00034 #include <QAction> 00035 00036 #include <iostream> 00037 00038 #include <typeinfo> 00039 00040 //#define DEBUG_COMMAND_HISTORY 1 00041 00042 CommandHistory *CommandHistory::m_instance = 0; 00043 00044 CommandHistory::CommandHistory() : 00045 m_undoLimit(50), 00046 m_redoLimit(50), 00047 m_menuLimit(15), 00048 m_savedAt(0), 00049 m_currentCompound(0), 00050 m_executeCompound(false), 00051 m_currentBundle(0), 00052 m_bundling(false), 00053 m_bundleTimer(0), 00054 m_bundleTimeout(3000) 00055 { 00056 m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); 00057 m_undoAction->setShortcut(tr("Ctrl+Z")); 00058 m_undoAction->setStatusTip(tr("Undo the last editing operation")); 00059 connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo())); 00060 00061 m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this); 00062 connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo())); 00063 00064 m_undoMenu = new QMenu(tr("&Undo")); 00065 m_undoMenuAction->setMenu(m_undoMenu); 00066 connect(m_undoMenu, SIGNAL(triggered(QAction *)), 00067 this, SLOT(undoActivated(QAction*))); 00068 00069 m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); 00070 m_redoAction->setShortcut(tr("Ctrl+Shift+Z")); 00071 m_redoAction->setStatusTip(tr("Redo the last operation that was undone")); 00072 connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo())); 00073 00074 m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this); 00075 connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo())); 00076 00077 m_redoMenu = new QMenu(tr("Re&do")); 00078 m_redoMenuAction->setMenu(m_redoMenu); 00079 connect(m_redoMenu, SIGNAL(triggered(QAction *)), 00080 this, SLOT(redoActivated(QAction*))); 00081 } 00082 00083 CommandHistory::~CommandHistory() 00084 { 00085 m_savedAt = -1; 00086 clearStack(m_undoStack); 00087 clearStack(m_redoStack); 00088 00089 delete m_undoMenu; 00090 delete m_redoMenu; 00091 } 00092 00093 CommandHistory * 00094 CommandHistory::getInstance() 00095 { 00096 if (!m_instance) m_instance = new CommandHistory(); 00097 return m_instance; 00098 } 00099 00100 void 00101 CommandHistory::clear() 00102 { 00103 #ifdef DEBUG_COMMAND_HISTORY 00104 cerr << "CommandHistory::clear()" << endl; 00105 #endif 00106 closeBundle(); 00107 m_savedAt = -1; 00108 clearStack(m_undoStack); 00109 clearStack(m_redoStack); 00110 updateActions(); 00111 } 00112 00113 void 00114 CommandHistory::registerMenu(QMenu *menu) 00115 { 00116 menu->addAction(m_undoAction); 00117 menu->addAction(m_redoAction); 00118 } 00119 00120 void 00121 CommandHistory::registerToolbar(QToolBar *toolbar) 00122 { 00123 toolbar->addAction(m_undoMenuAction); 00124 toolbar->addAction(m_redoMenuAction); 00125 } 00126 00127 void 00128 CommandHistory::addCommand(Command *command) 00129 { 00130 if (!command) return; 00131 00132 if (m_currentCompound) { 00133 addToCompound(command, m_executeCompound); 00134 return; 00135 } 00136 00137 addCommand(command, true); 00138 } 00139 00140 void 00141 CommandHistory::addCommand(Command *command, bool execute, bool bundle) 00142 { 00143 if (!command) return; 00144 00145 #ifdef DEBUG_COMMAND_HISTORY 00146 cerr << "CommandHistory::addCommand: " << command->getName() << " of type " << typeid(*command).name() << " at " << command << ": execute = " << execute << ", bundle = " << bundle << " (m_currentCompound = " << m_currentCompound << ", m_currentBundle = " << m_currentBundle << ")" << endl; 00147 #endif 00148 00149 if (m_currentCompound) { 00150 addToCompound(command, execute); 00151 return; 00152 } 00153 00154 if (bundle) { 00155 addToBundle(command, execute); 00156 return; 00157 } else if (m_currentBundle) { 00158 closeBundle(); 00159 } 00160 00161 #ifdef DEBUG_COMMAND_HISTORY 00162 if (!m_redoStack.empty()) { 00163 cerr << "CommandHistory::clearing redo stack" << endl; 00164 } 00165 #endif 00166 00167 // We can't redo after adding a command 00168 clearStack(m_redoStack); 00169 00170 // can we reach savedAt? 00171 if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope 00172 00173 m_undoStack.push(command); 00174 clipCommands(); 00175 00176 if (execute) { 00177 command->execute(); 00178 } 00179 00180 // Emit even if we aren't executing the command, because 00181 // someone must have executed it for this to make any sense 00182 emit commandExecuted(); 00183 emit commandExecuted(command); 00184 if (!m_bundling) emit activity(command->getName()); 00185 00186 updateActions(); 00187 } 00188 00189 void 00190 CommandHistory::addToBundle(Command *command, bool execute) 00191 { 00192 if (m_currentBundle) { 00193 if (!command || (command->getName() != m_currentBundleName)) { 00194 #ifdef DEBUG_COMMAND_HISTORY 00195 cerr << "CommandHistory::addToBundle: " << command->getName() 00196 << ": closing current bundle" << endl; 00197 #endif 00198 closeBundle(); 00199 } 00200 } 00201 00202 if (!command) return; 00203 00204 if (!m_currentBundle) { 00205 00206 #ifdef DEBUG_COMMAND_HISTORY 00207 cerr << "CommandHistory::addToBundle: " << command->getName() 00208 << ": creating new bundle" << endl; 00209 #endif 00210 00211 // need to addCommand before setting m_currentBundle, as addCommand 00212 // with bundle false will reset m_currentBundle to 0 00213 MacroCommand *mc = new BundleCommand(command->getName()); 00214 m_bundling = true; 00215 addCommand(mc, false); 00216 m_bundling = false; 00217 m_currentBundle = mc; 00218 m_currentBundleName = command->getName(); 00219 } 00220 00221 #ifdef DEBUG_COMMAND_HISTORY 00222 cerr << "CommandHistory::addToBundle: " << command->getName() 00223 << ": adding to bundle" << endl; 00224 #endif 00225 00226 if (execute) command->execute(); 00227 m_currentBundle->addCommand(command); 00228 00229 // Emit even if we aren't executing the command, because 00230 // someone must have executed it for this to make any sense 00231 emit commandExecuted(); 00232 emit commandExecuted(command); 00233 00234 updateActions(); 00235 00236 delete m_bundleTimer; 00237 m_bundleTimer = new QTimer(this); 00238 connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout())); 00239 m_bundleTimer->start(m_bundleTimeout); 00240 } 00241 00242 void 00243 CommandHistory::closeBundle() 00244 { 00245 if (m_currentBundle) { 00246 #ifdef DEBUG_COMMAND_HISTORY 00247 cerr << "CommandHistory::closeBundle" << endl; 00248 #endif 00249 emit activity(m_currentBundle->getName()); 00250 } 00251 m_currentBundle = 0; 00252 m_currentBundleName = ""; 00253 } 00254 00255 void 00256 CommandHistory::bundleTimerTimeout() 00257 { 00258 #ifdef DEBUG_COMMAND_HISTORY 00259 cerr << "CommandHistory::bundleTimerTimeout: bundle is " << m_currentBundle << endl; 00260 #endif 00261 00262 closeBundle(); 00263 } 00264 00265 void 00266 CommandHistory::addToCompound(Command *command, bool execute) 00267 { 00268 if (!m_currentCompound) { 00269 cerr << "CommandHistory::addToCompound: ERROR: no compound operation in progress!" << endl; 00270 return; 00271 } 00272 00273 #ifdef DEBUG_COMMAND_HISTORY 00274 cerr << "CommandHistory::addToCompound[" << m_currentCompound->getName() << "]: " << command->getName() << " (exec: " << execute << ")" << endl; 00275 #endif 00276 00277 if (execute) command->execute(); 00278 m_currentCompound->addCommand(command); 00279 } 00280 00281 void 00282 CommandHistory::startCompoundOperation(QString name, bool execute) 00283 { 00284 if (m_currentCompound) { 00285 cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << endl; 00286 cerr << "(name is " << m_currentCompound->getName() << ")" << endl; 00287 return; 00288 } 00289 00290 #ifdef DEBUG_COMMAND_HISTORY 00291 cerr << "CommandHistory::startCompoundOperation: " << name << " (exec: " << execute << ")" << endl; 00292 #endif 00293 00294 closeBundle(); 00295 00296 m_currentCompound = new MacroCommand(name); 00297 m_executeCompound = execute; 00298 } 00299 00300 void 00301 CommandHistory::endCompoundOperation() 00302 { 00303 if (!m_currentCompound) { 00304 cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << endl; 00305 return; 00306 } 00307 00308 #ifdef DEBUG_COMMAND_HISTORY 00309 cerr << "CommandHistory::endCompoundOperation: " << m_currentCompound->getName() << endl; 00310 #endif 00311 00312 MacroCommand *toAdd = m_currentCompound; 00313 m_currentCompound = 0; 00314 00315 if (toAdd->haveCommands()) { 00316 00317 // We don't execute the macro command here, because we have 00318 // been executing the individual commands as we went along if 00319 // m_executeCompound was true. 00320 addCommand(toAdd, false); 00321 } 00322 } 00323 00324 void 00325 CommandHistory::addExecutedCommand(Command *command) 00326 { 00327 addCommand(command, false); 00328 } 00329 00330 void 00331 CommandHistory::addCommandAndExecute(Command *command) 00332 { 00333 addCommand(command, true); 00334 } 00335 00336 void 00337 CommandHistory::undo() 00338 { 00339 if (m_undoStack.empty()) return; 00340 00341 #ifdef DEBUG_COMMAND_HISTORY 00342 cerr << "CommandHistory::undo()" << endl; 00343 #endif 00344 00345 closeBundle(); 00346 00347 Command *command = m_undoStack.top(); 00348 command->unexecute(); 00349 emit commandExecuted(); 00350 emit commandUnexecuted(command); 00351 emit activity(tr("Undo %1").arg(command->getName())); 00352 00353 m_redoStack.push(command); 00354 m_undoStack.pop(); 00355 00356 clipCommands(); 00357 updateActions(); 00358 00359 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); 00360 } 00361 00362 void 00363 CommandHistory::redo() 00364 { 00365 if (m_redoStack.empty()) return; 00366 00367 #ifdef DEBUG_COMMAND_HISTORY 00368 cerr << "CommandHistory::redo()" << endl; 00369 #endif 00370 00371 closeBundle(); 00372 00373 Command *command = m_redoStack.top(); 00374 command->execute(); 00375 emit commandExecuted(); 00376 emit commandExecuted(command); 00377 emit activity(tr("Redo %1").arg(command->getName())); 00378 00379 m_undoStack.push(command); 00380 m_redoStack.pop(); 00381 // no need to clip 00382 00383 updateActions(); 00384 00385 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored(); 00386 } 00387 00388 void 00389 CommandHistory::setUndoLimit(int limit) 00390 { 00391 if (limit > 0 && limit != m_undoLimit) { 00392 m_undoLimit = limit; 00393 clipCommands(); 00394 } 00395 } 00396 00397 void 00398 CommandHistory::setRedoLimit(int limit) 00399 { 00400 if (limit > 0 && limit != m_redoLimit) { 00401 m_redoLimit = limit; 00402 clipCommands(); 00403 } 00404 } 00405 00406 void 00407 CommandHistory::setMenuLimit(int limit) 00408 { 00409 m_menuLimit = limit; 00410 updateActions(); 00411 } 00412 00413 void 00414 CommandHistory::setBundleTimeout(int ms) 00415 { 00416 m_bundleTimeout = ms; 00417 } 00418 00419 void 00420 CommandHistory::documentSaved() 00421 { 00422 closeBundle(); 00423 m_savedAt = m_undoStack.size(); 00424 } 00425 00426 void 00427 CommandHistory::clipCommands() 00428 { 00429 if ((int)m_undoStack.size() > m_undoLimit) { 00430 m_savedAt -= (m_undoStack.size() - m_undoLimit); 00431 } 00432 00433 clipStack(m_undoStack, m_undoLimit); 00434 clipStack(m_redoStack, m_redoLimit); 00435 } 00436 00437 void 00438 CommandHistory::clipStack(CommandStack &stack, int limit) 00439 { 00440 int i; 00441 00442 if ((int)stack.size() > limit) { 00443 00444 CommandStack tempStack; 00445 00446 for (i = 0; i < limit; ++i) { 00447 #ifdef DEBUG_COMMAND_HISTORY 00448 Command *command = stack.top(); 00449 cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName() << " at " << command << endl; 00450 #endif 00451 tempStack.push(stack.top()); 00452 stack.pop(); 00453 } 00454 00455 clearStack(stack); 00456 00457 for (i = 0; i < m_undoLimit; ++i) { 00458 stack.push(tempStack.top()); 00459 tempStack.pop(); 00460 } 00461 } 00462 } 00463 00464 void 00465 CommandHistory::clearStack(CommandStack &stack) 00466 { 00467 while (!stack.empty()) { 00468 Command *command = stack.top(); 00469 // Not safe to call getName() on a command about to be deleted 00470 #ifdef DEBUG_COMMAND_HISTORY 00471 cerr << "CommandHistory::clearStack: About to delete command " << command << endl; 00472 #endif 00473 delete command; 00474 stack.pop(); 00475 } 00476 } 00477 00478 void 00479 CommandHistory::undoActivated(QAction *action) 00480 { 00481 int pos = m_actionCounts[action]; 00482 for (int i = 0; i <= pos; ++i) { 00483 undo(); 00484 } 00485 } 00486 00487 void 00488 CommandHistory::redoActivated(QAction *action) 00489 { 00490 int pos = m_actionCounts[action]; 00491 for (int i = 0; i <= pos; ++i) { 00492 redo(); 00493 } 00494 } 00495 00496 void 00497 CommandHistory::updateActions() 00498 { 00499 m_actionCounts.clear(); 00500 00501 for (int undo = 0; undo <= 1; ++undo) { 00502 00503 QAction *action(undo ? m_undoAction : m_redoAction); 00504 QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction); 00505 QMenu *menu(undo ? m_undoMenu : m_redoMenu); 00506 CommandStack &stack(undo ? m_undoStack : m_redoStack); 00507 00508 if (stack.empty()) { 00509 00510 QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo")); 00511 00512 action->setEnabled(false); 00513 action->setText(text); 00514 00515 menuAction->setEnabled(false); 00516 menuAction->setText(text); 00517 00518 } else { 00519 00520 action->setEnabled(true); 00521 menuAction->setEnabled(true); 00522 00523 QString commandName = stack.top()->getName(); 00524 commandName.replace(QRegExp("&"), ""); 00525 00526 QString text = (undo ? tr("&Undo %1") : tr("Re&do %1")) 00527 .arg(commandName); 00528 00529 action->setText(text); 00530 menuAction->setText(text); 00531 } 00532 00533 menu->clear(); 00534 00535 CommandStack tempStack; 00536 int j = 0; 00537 00538 while (j < m_menuLimit && !stack.empty()) { 00539 00540 Command *command = stack.top(); 00541 tempStack.push(command); 00542 stack.pop(); 00543 00544 QString commandName = command->getName(); 00545 commandName.replace(QRegExp("&"), ""); 00546 00547 QString text; 00548 if (undo) text = tr("&Undo %1").arg(commandName); 00549 else text = tr("Re&do %1").arg(commandName); 00550 00551 QAction *action = menu->addAction(text); 00552 m_actionCounts[action] = j++; 00553 } 00554 00555 while (!tempStack.empty()) { 00556 stack.push(tempStack.top()); 00557 tempStack.pop(); 00558 } 00559 } 00560 } 00561