CommandHistory.cpp

Go to the documentation of this file.
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 "Command.h"
00028 
00029 #include <QRegExp>
00030 #include <QMenu>
00031 #include <QToolBar>
00032 #include <QString>
00033 #include <QTimer>
00034 
00035 #include <iostream>
00036 
00037 CommandHistory *CommandHistory::m_instance = 0;
00038 
00039 CommandHistory::CommandHistory() :
00040     m_undoLimit(50),
00041     m_redoLimit(50),
00042     m_menuLimit(15),
00043     m_savedAt(0),
00044     m_currentCompound(0),
00045     m_executeCompound(false),
00046     m_currentBundle(0),
00047     m_bundleTimer(0),
00048     m_bundleTimeout(5000)
00049 {
00050     m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
00051     m_undoAction->setShortcut(tr("Ctrl+Z"));
00052     m_undoAction->setStatusTip(tr("Undo the last editing operation"));
00053     connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo()));
00054     
00055     m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
00056     connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo()));
00057     
00058     m_undoMenu = new QMenu(tr("&Undo"));
00059     m_undoMenuAction->setMenu(m_undoMenu);
00060     connect(m_undoMenu, SIGNAL(triggered(QAction *)),
00061             this, SLOT(undoActivated(QAction*)));
00062 
00063     m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
00064     m_redoAction->setShortcut(tr("Ctrl+Shift+Z"));
00065     m_redoAction->setStatusTip(tr("Redo the last operation that was undone"));
00066     connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo()));
00067     
00068     m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
00069     connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo()));
00070 
00071     m_redoMenu = new QMenu(tr("Re&do"));
00072     m_redoMenuAction->setMenu(m_redoMenu);
00073     connect(m_redoMenu, SIGNAL(triggered(QAction *)),
00074             this, SLOT(redoActivated(QAction*)));
00075 }
00076 
00077 CommandHistory::~CommandHistory()
00078 {
00079     m_savedAt = -1;
00080     clearStack(m_undoStack);
00081     clearStack(m_redoStack);
00082 
00083     delete m_undoMenu;
00084     delete m_redoMenu;
00085 }
00086 
00087 CommandHistory *
00088 CommandHistory::getInstance()
00089 {
00090     if (!m_instance) m_instance = new CommandHistory();
00091     return m_instance;
00092 }
00093 
00094 void
00095 CommandHistory::clear()
00096 {
00097 //    std::cerr << "CommandHistory::clear()" << std::endl;
00098     closeBundle();
00099     m_savedAt = -1;
00100     clearStack(m_undoStack);
00101     clearStack(m_redoStack);
00102     updateActions();
00103 }
00104 
00105 void
00106 CommandHistory::registerMenu(QMenu *menu)
00107 {
00108     menu->addAction(m_undoAction);
00109     menu->addAction(m_redoAction);
00110 }
00111 
00112 void
00113 CommandHistory::registerToolbar(QToolBar *toolbar)
00114 {
00115     toolbar->addAction(m_undoMenuAction);
00116     toolbar->addAction(m_redoMenuAction);
00117 }
00118 
00119 void
00120 CommandHistory::addCommand(Command *command)
00121 {
00122     if (!command) return;
00123 
00124     if (m_currentCompound) {
00125         addToCompound(command, m_executeCompound);
00126         return;
00127     }
00128 
00129     addCommand(command, true);
00130 }
00131 
00132 void
00133 CommandHistory::addCommand(Command *command, bool execute, bool bundle)
00134 {
00135     if (!command) return;
00136 
00137     if (m_currentCompound) {
00138         addToCompound(command, execute);
00139         return;
00140     }
00141 
00142     if (bundle) {
00143         addToBundle(command, execute);
00144         return;
00145     } else if (m_currentBundle) {
00146         closeBundle();
00147     }
00148 
00149 //    std::cerr << "CommandHistory::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
00150 
00151     // We can't redo after adding a command
00152 //    std::cerr << "CommandHistory::clearing redo stack" << std::endl;
00153     clearStack(m_redoStack);
00154 
00155     // can we reach savedAt?
00156     if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope
00157 
00158     m_undoStack.push(command);
00159     clipCommands();
00160     
00161     if (execute) {
00162         command->execute();
00163     }
00164 
00165     // Emit even if we aren't executing the command, because
00166     // someone must have executed it for this to make any sense
00167     emit commandExecuted();
00168     emit commandExecuted(command);
00169 
00170     updateActions();
00171 }
00172 
00173 void
00174 CommandHistory::addToBundle(Command *command, bool execute)
00175 {
00176     if (m_currentBundle) {
00177         if (!command || (command->getName() != m_currentBundleName)) {
00178             closeBundle();
00179         }
00180     }
00181 
00182     if (!command) return;
00183 
00184     if (!m_currentBundle) {
00185         // need to addCommand before setting m_currentBundle, as addCommand
00186         // with bundle false will reset m_currentBundle to 0
00187         MacroCommand *mc = new MacroCommand(command->getName());
00188         addCommand(mc, false);
00189         m_currentBundle = mc;
00190         m_currentBundleName = command->getName();
00191     }
00192 
00193     if (execute) command->execute();
00194     m_currentBundle->addCommand(command);
00195 
00196     delete m_bundleTimer;
00197     m_bundleTimer = new QTimer(this);
00198     connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout()));
00199     m_bundleTimer->start(m_bundleTimeout);
00200 }
00201 
00202 void
00203 CommandHistory::closeBundle()
00204 {
00205     m_currentBundle = 0;
00206     m_currentBundleName = "";
00207 }
00208 
00209 void
00210 CommandHistory::bundleTimerTimeout()
00211 {
00212     closeBundle();
00213 }
00214 
00215 void
00216 CommandHistory::addToCompound(Command *command, bool execute)
00217 {
00218 //    std::cerr << "CommandHistory::addToCompound: " << command->getName().toLocal8Bit().data() << std::endl;
00219     if (!m_currentCompound) {
00220         std::cerr << "CommandHistory::addToCompound: ERROR: no compound operation in progress!" << std::endl;
00221         return;
00222     }
00223 
00224     if (execute) command->execute();
00225     m_currentCompound->addCommand(command);
00226 }
00227 
00228 void
00229 CommandHistory::startCompoundOperation(QString name, bool execute)
00230 {
00231     if (m_currentCompound) {
00232         std::cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl;
00233         std::cerr << "(name is " << m_currentCompound->getName().toLocal8Bit().data() << ")" << std::endl;
00234         return;
00235     }
00236  
00237     closeBundle();
00238    
00239     m_currentCompound = new MacroCommand(name);
00240     m_executeCompound = execute;
00241 }
00242 
00243 void
00244 CommandHistory::endCompoundOperation()
00245 {
00246     if (!m_currentCompound) {
00247         std::cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl;
00248         return;
00249     }
00250 
00251     MacroCommand *toAdd = m_currentCompound;
00252     m_currentCompound = 0;
00253 
00254     if (toAdd->haveCommands()) {
00255 
00256         // We don't execute the macro command here, because we have
00257         // been executing the individual commands as we went along if
00258         // m_executeCompound was true.
00259         addCommand(toAdd, false);
00260     }
00261 }    
00262 
00263 void
00264 CommandHistory::addExecutedCommand(Command *command)
00265 {
00266     addCommand(command, false);
00267 }
00268 
00269 void
00270 CommandHistory::addCommandAndExecute(Command *command)
00271 {
00272     addCommand(command, true);
00273 }
00274 
00275 void
00276 CommandHistory::undo()
00277 {
00278     if (m_undoStack.empty()) return;
00279 
00280     closeBundle();
00281 
00282     Command *command = m_undoStack.top();
00283     command->unexecute();
00284     emit commandExecuted();
00285     emit commandUnexecuted(command);
00286 
00287     m_redoStack.push(command);
00288     m_undoStack.pop();
00289 
00290     clipCommands();
00291     updateActions();
00292 
00293     if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
00294 }
00295 
00296 void
00297 CommandHistory::redo()
00298 {
00299     if (m_redoStack.empty()) return;
00300 
00301     closeBundle();
00302 
00303     Command *command = m_redoStack.top();
00304     command->execute();
00305     emit commandExecuted();
00306     emit commandExecuted(command);
00307 
00308     m_undoStack.push(command);
00309     m_redoStack.pop();
00310     // no need to clip
00311 
00312     updateActions();
00313 
00314     if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
00315 }
00316 
00317 void
00318 CommandHistory::setUndoLimit(int limit)
00319 {
00320     if (limit > 0 && limit != m_undoLimit) {
00321         m_undoLimit = limit;
00322         clipCommands();
00323     }
00324 }
00325 
00326 void
00327 CommandHistory::setRedoLimit(int limit)
00328 {
00329     if (limit > 0 && limit != m_redoLimit) {
00330         m_redoLimit = limit;
00331         clipCommands();
00332     }
00333 }
00334 
00335 void
00336 CommandHistory::setMenuLimit(int limit)
00337 {
00338     m_menuLimit = limit;
00339     updateActions();
00340 }
00341 
00342 void
00343 CommandHistory::setBundleTimeout(int ms)
00344 {
00345     m_bundleTimeout = ms;
00346 }
00347 
00348 void
00349 CommandHistory::documentSaved()
00350 {
00351     closeBundle();
00352     m_savedAt = m_undoStack.size();
00353 }
00354 
00355 void
00356 CommandHistory::clipCommands()
00357 {
00358     if ((int)m_undoStack.size() > m_undoLimit) {
00359         m_savedAt -= (m_undoStack.size() - m_undoLimit);
00360     }
00361 
00362     clipStack(m_undoStack, m_undoLimit);
00363     clipStack(m_redoStack, m_redoLimit);
00364 }
00365 
00366 void
00367 CommandHistory::clipStack(CommandStack &stack, int limit)
00368 {
00369     int i;
00370 
00371     if ((int)stack.size() > limit) {
00372 
00373         CommandStack tempStack;
00374 
00375         for (i = 0; i < limit; ++i) {
00376 //          Command *command = stack.top();
00377 //          std::cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
00378             tempStack.push(stack.top());
00379             stack.pop();
00380         }
00381 
00382         clearStack(stack);
00383 
00384         for (i = 0; i < m_undoLimit; ++i) {
00385             stack.push(tempStack.top());
00386             tempStack.pop();
00387         }
00388     }
00389 }
00390 
00391 void
00392 CommandHistory::clearStack(CommandStack &stack)
00393 {
00394     while (!stack.empty()) {
00395         Command *command = stack.top();
00396         // Not safe to call getName() on a command about to be deleted
00397 //      std::cerr << "CommandHistory::clearStack: About to delete command " << command << std::endl;
00398         delete command;
00399         stack.pop();
00400     }
00401 }
00402 
00403 void
00404 CommandHistory::undoActivated(QAction *action)
00405 {
00406     int pos = m_actionCounts[action];
00407     for (int i = 0; i <= pos; ++i) {
00408         undo();
00409     }
00410 }
00411 
00412 void
00413 CommandHistory::redoActivated(QAction *action)
00414 {
00415     int pos = m_actionCounts[action];
00416     for (int i = 0; i <= pos; ++i) {
00417         redo();
00418     }
00419 }
00420 
00421 void
00422 CommandHistory::updateActions()
00423 {
00424     m_actionCounts.clear();
00425 
00426     for (int undo = 0; undo <= 1; ++undo) {
00427 
00428         QAction *action(undo ? m_undoAction : m_redoAction);
00429         QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction);
00430         QMenu *menu(undo ? m_undoMenu : m_redoMenu);
00431         CommandStack &stack(undo ? m_undoStack : m_redoStack);
00432 
00433         if (stack.empty()) {
00434 
00435             QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo"));
00436 
00437             action->setEnabled(false);
00438             action->setText(text);
00439 
00440             menuAction->setEnabled(false);
00441             menuAction->setText(text);
00442 
00443         } else {
00444 
00445             action->setEnabled(true);
00446             menuAction->setEnabled(true);
00447 
00448             QString commandName = stack.top()->getName();
00449             commandName.replace(QRegExp("&"), "");
00450 
00451             QString text = (undo ? tr("&Undo %1") : tr("Re&do %1"))
00452                 .arg(commandName);
00453 
00454             action->setText(text);
00455             menuAction->setText(text);
00456         }
00457 
00458         menu->clear();
00459 
00460         CommandStack tempStack;
00461         int j = 0;
00462 
00463         while (j < m_menuLimit && !stack.empty()) {
00464 
00465             Command *command = stack.top();
00466             tempStack.push(command);
00467             stack.pop();
00468 
00469             QString commandName = command->getName();
00470             commandName.replace(QRegExp("&"), "");
00471 
00472             QString text;
00473             if (undo) text = tr("&Undo %1").arg(commandName);
00474             else      text = tr("Re&do %1").arg(commandName);
00475             
00476             QAction *action = menu->addAction(text);
00477             m_actionCounts[action] = j++;
00478         }
00479 
00480         while (!tempStack.empty()) {
00481             stack.push(tempStack.top());
00482             tempStack.pop();
00483         }
00484     }
00485 }
00486 

Generated on Wed Feb 20 15:45:25 2008 for SonicVisualiser by  doxygen 1.5.1