00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
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
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
00150
00151
00152
00153 clearStack(m_redoStack);
00154
00155
00156 if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1;
00157
00158 m_undoStack.push(command);
00159 clipCommands();
00160
00161 if (execute) {
00162 command->execute();
00163 }
00164
00165
00166
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
00186
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
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
00257
00258
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
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
00377
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
00397
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