annotate widgets/CommandHistory.cpp @ 738:d26545a2a02d tonioni

Rather than undrawing the bottom bit of the dial, don't draw it in the first place (necessary on shaded background)
author Chris Cannam
date Thu, 06 Mar 2014 13:52:33 +0000
parents 692a8b9804fe
children f428bd852580
rev   line source
Chris@376 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@376 2
Chris@376 3 /*
Chris@376 4 Sonic Visualiser
Chris@376 5 An audio file viewer and annotation editor.
Chris@376 6 Centre for Digital Music, Queen Mary, University of London.
Chris@376 7
Chris@376 8 This program is free software; you can redistribute it and/or
Chris@376 9 modify it under the terms of the GNU General Public License as
Chris@376 10 published by the Free Software Foundation; either version 2 of the
Chris@376 11 License, or (at your option) any later version. See the file
Chris@376 12 COPYING included with this distribution for more information.
Chris@376 13 */
Chris@376 14
Chris@376 15 /*
Chris@376 16 This is a modified version of a source file from the Rosegarden
Chris@376 17 MIDI and audio sequencer and notation editor, copyright 2000-2006
Chris@376 18 Chris Cannam, distributed under the GNU General Public License.
Chris@376 19
Chris@376 20 This file contains traces of the KCommandHistory class from the KDE
Chris@376 21 project, copyright 2000 Werner Trobin and David Faure and
Chris@376 22 distributed under the GNU Lesser General Public License.
Chris@376 23 */
Chris@376 24
Chris@376 25 #include "CommandHistory.h"
Chris@376 26
Chris@376 27 #include "base/Command.h"
Chris@376 28
Chris@376 29 #include <QRegExp>
Chris@376 30 #include <QMenu>
Chris@376 31 #include <QToolBar>
Chris@376 32 #include <QString>
Chris@376 33 #include <QTimer>
Chris@376 34 #include <QAction>
Chris@376 35
Chris@376 36 #include <iostream>
Chris@376 37
Chris@503 38 #include <typeinfo>
Chris@503 39
Chris@514 40 //#define DEBUG_COMMAND_HISTORY 1
Chris@377 41
Chris@376 42 CommandHistory *CommandHistory::m_instance = 0;
Chris@376 43
Chris@376 44 CommandHistory::CommandHistory() :
Chris@376 45 m_undoLimit(50),
Chris@376 46 m_redoLimit(50),
Chris@376 47 m_menuLimit(15),
Chris@376 48 m_savedAt(0),
Chris@376 49 m_currentCompound(0),
Chris@376 50 m_executeCompound(false),
Chris@376 51 m_currentBundle(0),
Chris@502 52 m_bundling(false),
Chris@376 53 m_bundleTimer(0),
Chris@502 54 m_bundleTimeout(3000)
Chris@376 55 {
Chris@376 56 m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
Chris@376 57 m_undoAction->setShortcut(tr("Ctrl+Z"));
Chris@376 58 m_undoAction->setStatusTip(tr("Undo the last editing operation"));
Chris@376 59 connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo()));
Chris@376 60
Chris@376 61 m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
Chris@376 62 connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo()));
Chris@376 63
Chris@376 64 m_undoMenu = new QMenu(tr("&Undo"));
Chris@376 65 m_undoMenuAction->setMenu(m_undoMenu);
Chris@376 66 connect(m_undoMenu, SIGNAL(triggered(QAction *)),
Chris@376 67 this, SLOT(undoActivated(QAction*)));
Chris@376 68
Chris@376 69 m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
Chris@376 70 m_redoAction->setShortcut(tr("Ctrl+Shift+Z"));
Chris@376 71 m_redoAction->setStatusTip(tr("Redo the last operation that was undone"));
Chris@376 72 connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo()));
Chris@376 73
Chris@376 74 m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
Chris@376 75 connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo()));
Chris@376 76
Chris@376 77 m_redoMenu = new QMenu(tr("Re&do"));
Chris@376 78 m_redoMenuAction->setMenu(m_redoMenu);
Chris@376 79 connect(m_redoMenu, SIGNAL(triggered(QAction *)),
Chris@376 80 this, SLOT(redoActivated(QAction*)));
Chris@376 81 }
Chris@376 82
Chris@376 83 CommandHistory::~CommandHistory()
Chris@376 84 {
Chris@376 85 m_savedAt = -1;
Chris@376 86 clearStack(m_undoStack);
Chris@376 87 clearStack(m_redoStack);
Chris@376 88
Chris@376 89 delete m_undoMenu;
Chris@376 90 delete m_redoMenu;
Chris@376 91 }
Chris@376 92
Chris@376 93 CommandHistory *
Chris@376 94 CommandHistory::getInstance()
Chris@376 95 {
Chris@376 96 if (!m_instance) m_instance = new CommandHistory();
Chris@376 97 return m_instance;
Chris@376 98 }
Chris@376 99
Chris@376 100 void
Chris@376 101 CommandHistory::clear()
Chris@376 102 {
Chris@377 103 #ifdef DEBUG_COMMAND_HISTORY
Chris@587 104 SVDEBUG << "CommandHistory::clear()" << endl;
Chris@377 105 #endif
Chris@376 106 closeBundle();
Chris@376 107 m_savedAt = -1;
Chris@376 108 clearStack(m_undoStack);
Chris@376 109 clearStack(m_redoStack);
Chris@376 110 updateActions();
Chris@376 111 }
Chris@376 112
Chris@376 113 void
Chris@376 114 CommandHistory::registerMenu(QMenu *menu)
Chris@376 115 {
Chris@376 116 menu->addAction(m_undoAction);
Chris@376 117 menu->addAction(m_redoAction);
Chris@376 118 }
Chris@376 119
Chris@376 120 void
Chris@376 121 CommandHistory::registerToolbar(QToolBar *toolbar)
Chris@376 122 {
Chris@376 123 toolbar->addAction(m_undoMenuAction);
Chris@376 124 toolbar->addAction(m_redoMenuAction);
Chris@376 125 }
Chris@376 126
Chris@376 127 void
Chris@376 128 CommandHistory::addCommand(Command *command)
Chris@376 129 {
Chris@376 130 if (!command) return;
Chris@376 131
Chris@376 132 if (m_currentCompound) {
Chris@376 133 addToCompound(command, m_executeCompound);
Chris@376 134 return;
Chris@376 135 }
Chris@376 136
Chris@376 137 addCommand(command, true);
Chris@376 138 }
Chris@376 139
Chris@376 140 void
Chris@376 141 CommandHistory::addCommand(Command *command, bool execute, bool bundle)
Chris@376 142 {
Chris@376 143 if (!command) return;
Chris@376 144
Chris@397 145 #ifdef DEBUG_COMMAND_HISTORY
Chris@683 146 SVDEBUG << "CommandHistory::addCommand: " << command->getName() << " of type " << typeid(*command).name() << " at " << command << ": execute = " << execute << ", bundle = " << bundle << " (m_currentCompound = " << m_currentCompound << ", m_currentBundle = " << m_currentBundle << ")" << endl;
Chris@397 147 #endif
Chris@397 148
Chris@376 149 if (m_currentCompound) {
Chris@376 150 addToCompound(command, execute);
Chris@376 151 return;
Chris@376 152 }
Chris@376 153
Chris@376 154 if (bundle) {
Chris@376 155 addToBundle(command, execute);
Chris@376 156 return;
Chris@376 157 } else if (m_currentBundle) {
Chris@376 158 closeBundle();
Chris@376 159 }
Chris@376 160
Chris@377 161 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 162 if (!m_redoStack.empty()) {
Chris@587 163 SVDEBUG << "CommandHistory::clearing redo stack" << endl;
Chris@377 164 }
Chris@377 165 #endif
Chris@376 166
Chris@376 167 // We can't redo after adding a command
Chris@376 168 clearStack(m_redoStack);
Chris@376 169
Chris@376 170 // can we reach savedAt?
Chris@376 171 if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope
Chris@376 172
Chris@376 173 m_undoStack.push(command);
Chris@376 174 clipCommands();
Chris@376 175
Chris@376 176 if (execute) {
Chris@376 177 command->execute();
Chris@376 178 }
Chris@376 179
Chris@376 180 // Emit even if we aren't executing the command, because
Chris@376 181 // someone must have executed it for this to make any sense
Chris@376 182 emit commandExecuted();
Chris@376 183 emit commandExecuted(command);
Chris@502 184 if (!m_bundling) emit activity(command->getName());
Chris@502 185
Chris@376 186 updateActions();
Chris@376 187 }
Chris@376 188
Chris@376 189 void
Chris@376 190 CommandHistory::addToBundle(Command *command, bool execute)
Chris@376 191 {
Chris@376 192 if (m_currentBundle) {
Chris@376 193 if (!command || (command->getName() != m_currentBundleName)) {
Chris@377 194 #ifdef DEBUG_COMMAND_HISTORY
Chris@587 195 SVDEBUG << "CommandHistory::addToBundle: "
Chris@585 196 << command->getName() << ": closing current bundle" << endl;
Chris@377 197 #endif
Chris@376 198 closeBundle();
Chris@376 199 }
Chris@376 200 }
Chris@376 201
Chris@376 202 if (!command) return;
Chris@376 203
Chris@376 204 if (!m_currentBundle) {
Chris@377 205
Chris@377 206 #ifdef DEBUG_COMMAND_HISTORY
Chris@587 207 SVDEBUG << "CommandHistory::addToBundle: "
Chris@585 208 << command->getName() << ": creating new bundle" << endl;
Chris@377 209 #endif
Chris@377 210
Chris@376 211 // need to addCommand before setting m_currentBundle, as addCommand
Chris@376 212 // with bundle false will reset m_currentBundle to 0
Chris@397 213 MacroCommand *mc = new BundleCommand(command->getName());
Chris@502 214 m_bundling = true;
Chris@376 215 addCommand(mc, false);
Chris@502 216 m_bundling = false;
Chris@376 217 m_currentBundle = mc;
Chris@376 218 m_currentBundleName = command->getName();
Chris@376 219 }
Chris@376 220
Chris@377 221 #ifdef DEBUG_COMMAND_HISTORY
Chris@587 222 SVDEBUG << "CommandHistory::addToBundle: "
Chris@585 223 << command->getName() << ": adding to bundle" << endl;
Chris@377 224 #endif
Chris@377 225
Chris@376 226 if (execute) command->execute();
Chris@376 227 m_currentBundle->addCommand(command);
Chris@376 228
Chris@377 229 // Emit even if we aren't executing the command, because
Chris@377 230 // someone must have executed it for this to make any sense
Chris@377 231 emit commandExecuted();
Chris@377 232 emit commandExecuted(command);
Chris@377 233
Chris@377 234 updateActions();
Chris@377 235
Chris@376 236 delete m_bundleTimer;
Chris@376 237 m_bundleTimer = new QTimer(this);
Chris@376 238 connect(m_bundleTimer, SIGNAL(timeout()), this, SLOT(bundleTimerTimeout()));
Chris@376 239 m_bundleTimer->start(m_bundleTimeout);
Chris@376 240 }
Chris@376 241
Chris@376 242 void
Chris@376 243 CommandHistory::closeBundle()
Chris@376 244 {
Chris@377 245 #ifdef DEBUG_COMMAND_HISTORY
Chris@587 246 SVDEBUG << "CommandHistory::closeBundle" << endl;
Chris@377 247 #endif
Chris@377 248
Chris@502 249 if (m_currentBundle) emit activity(m_currentBundle->getName());
Chris@376 250 m_currentBundle = 0;
Chris@376 251 m_currentBundleName = "";
Chris@376 252 }
Chris@376 253
Chris@376 254 void
Chris@376 255 CommandHistory::bundleTimerTimeout()
Chris@376 256 {
Chris@377 257 #ifdef DEBUG_COMMAND_HISTORY
Chris@587 258 SVDEBUG << "CommandHistory::bundleTimerTimeout: bundle is " << m_currentBundle << endl;
Chris@377 259 #endif
Chris@377 260
Chris@376 261 closeBundle();
Chris@376 262 }
Chris@376 263
Chris@376 264 void
Chris@376 265 CommandHistory::addToCompound(Command *command, bool execute)
Chris@376 266 {
Chris@377 267 #ifdef DEBUG_COMMAND_HISTORY
Chris@683 268 SVDEBUG << "CommandHistory::addToCompound: " << command->getName() << endl;
Chris@377 269 #endif
Chris@376 270 if (!m_currentCompound) {
Chris@587 271 SVDEBUG << "CommandHistory::addToCompound: ERROR: no compound operation in progress!" << endl;
Chris@376 272 return;
Chris@376 273 }
Chris@376 274
Chris@376 275 if (execute) command->execute();
Chris@376 276 m_currentCompound->addCommand(command);
Chris@376 277 }
Chris@376 278
Chris@376 279 void
Chris@376 280 CommandHistory::startCompoundOperation(QString name, bool execute)
Chris@376 281 {
Chris@376 282 if (m_currentCompound) {
Chris@587 283 SVDEBUG << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << endl;
Chris@683 284 cerr << "(name is " << m_currentCompound->getName() << ")" << endl;
Chris@376 285 return;
Chris@376 286 }
Chris@376 287
Chris@376 288 closeBundle();
Chris@376 289
Chris@376 290 m_currentCompound = new MacroCommand(name);
Chris@376 291 m_executeCompound = execute;
Chris@376 292 }
Chris@376 293
Chris@376 294 void
Chris@376 295 CommandHistory::endCompoundOperation()
Chris@376 296 {
Chris@376 297 if (!m_currentCompound) {
Chris@587 298 SVDEBUG << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << endl;
Chris@376 299 return;
Chris@376 300 }
Chris@376 301
Chris@376 302 MacroCommand *toAdd = m_currentCompound;
Chris@376 303 m_currentCompound = 0;
Chris@376 304
Chris@376 305 if (toAdd->haveCommands()) {
Chris@376 306
Chris@376 307 // We don't execute the macro command here, because we have
Chris@376 308 // been executing the individual commands as we went along if
Chris@376 309 // m_executeCompound was true.
Chris@376 310 addCommand(toAdd, false);
Chris@376 311 }
Chris@376 312 }
Chris@376 313
Chris@376 314 void
Chris@376 315 CommandHistory::addExecutedCommand(Command *command)
Chris@376 316 {
Chris@376 317 addCommand(command, false);
Chris@376 318 }
Chris@376 319
Chris@376 320 void
Chris@376 321 CommandHistory::addCommandAndExecute(Command *command)
Chris@376 322 {
Chris@376 323 addCommand(command, true);
Chris@376 324 }
Chris@376 325
Chris@376 326 void
Chris@376 327 CommandHistory::undo()
Chris@376 328 {
Chris@376 329 if (m_undoStack.empty()) return;
Chris@376 330
Chris@377 331 #ifdef DEBUG_COMMAND_HISTORY
Chris@587 332 SVDEBUG << "CommandHistory::undo()" << endl;
Chris@377 333 #endif
Chris@377 334
Chris@376 335 closeBundle();
Chris@376 336
Chris@376 337 Command *command = m_undoStack.top();
Chris@376 338 command->unexecute();
Chris@376 339 emit commandExecuted();
Chris@376 340 emit commandUnexecuted(command);
Chris@502 341 emit activity(tr("Undo %1").arg(command->getName()));
Chris@376 342
Chris@376 343 m_redoStack.push(command);
Chris@376 344 m_undoStack.pop();
Chris@376 345
Chris@376 346 clipCommands();
Chris@376 347 updateActions();
Chris@376 348
Chris@376 349 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@376 350 }
Chris@376 351
Chris@376 352 void
Chris@376 353 CommandHistory::redo()
Chris@376 354 {
Chris@376 355 if (m_redoStack.empty()) return;
Chris@376 356
Chris@377 357 #ifdef DEBUG_COMMAND_HISTORY
Chris@587 358 SVDEBUG << "CommandHistory::redo()" << endl;
Chris@377 359 #endif
Chris@377 360
Chris@376 361 closeBundle();
Chris@376 362
Chris@376 363 Command *command = m_redoStack.top();
Chris@376 364 command->execute();
Chris@376 365 emit commandExecuted();
Chris@376 366 emit commandExecuted(command);
Chris@502 367 emit activity(tr("Redo %1").arg(command->getName()));
Chris@376 368
Chris@376 369 m_undoStack.push(command);
Chris@376 370 m_redoStack.pop();
Chris@376 371 // no need to clip
Chris@376 372
Chris@376 373 updateActions();
Chris@376 374
Chris@376 375 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@376 376 }
Chris@376 377
Chris@376 378 void
Chris@376 379 CommandHistory::setUndoLimit(int limit)
Chris@376 380 {
Chris@376 381 if (limit > 0 && limit != m_undoLimit) {
Chris@376 382 m_undoLimit = limit;
Chris@376 383 clipCommands();
Chris@376 384 }
Chris@376 385 }
Chris@376 386
Chris@376 387 void
Chris@376 388 CommandHistory::setRedoLimit(int limit)
Chris@376 389 {
Chris@376 390 if (limit > 0 && limit != m_redoLimit) {
Chris@376 391 m_redoLimit = limit;
Chris@376 392 clipCommands();
Chris@376 393 }
Chris@376 394 }
Chris@376 395
Chris@376 396 void
Chris@376 397 CommandHistory::setMenuLimit(int limit)
Chris@376 398 {
Chris@376 399 m_menuLimit = limit;
Chris@376 400 updateActions();
Chris@376 401 }
Chris@376 402
Chris@376 403 void
Chris@376 404 CommandHistory::setBundleTimeout(int ms)
Chris@376 405 {
Chris@376 406 m_bundleTimeout = ms;
Chris@376 407 }
Chris@376 408
Chris@376 409 void
Chris@376 410 CommandHistory::documentSaved()
Chris@376 411 {
Chris@376 412 closeBundle();
Chris@376 413 m_savedAt = m_undoStack.size();
Chris@376 414 }
Chris@376 415
Chris@376 416 void
Chris@376 417 CommandHistory::clipCommands()
Chris@376 418 {
Chris@376 419 if ((int)m_undoStack.size() > m_undoLimit) {
Chris@376 420 m_savedAt -= (m_undoStack.size() - m_undoLimit);
Chris@376 421 }
Chris@376 422
Chris@376 423 clipStack(m_undoStack, m_undoLimit);
Chris@376 424 clipStack(m_redoStack, m_redoLimit);
Chris@376 425 }
Chris@376 426
Chris@376 427 void
Chris@376 428 CommandHistory::clipStack(CommandStack &stack, int limit)
Chris@376 429 {
Chris@376 430 int i;
Chris@376 431
Chris@376 432 if ((int)stack.size() > limit) {
Chris@376 433
Chris@376 434 CommandStack tempStack;
Chris@376 435
Chris@376 436 for (i = 0; i < limit; ++i) {
Chris@377 437 #ifdef DEBUG_COMMAND_HISTORY
Chris@377 438 Command *command = stack.top();
Chris@683 439 SVDEBUG << "CommandHistory::clipStack: Saving recent command: " << command->getName() << " at " << command << endl;
Chris@377 440 #endif
Chris@376 441 tempStack.push(stack.top());
Chris@376 442 stack.pop();
Chris@376 443 }
Chris@376 444
Chris@376 445 clearStack(stack);
Chris@376 446
Chris@376 447 for (i = 0; i < m_undoLimit; ++i) {
Chris@376 448 stack.push(tempStack.top());
Chris@376 449 tempStack.pop();
Chris@376 450 }
Chris@376 451 }
Chris@376 452 }
Chris@376 453
Chris@376 454 void
Chris@376 455 CommandHistory::clearStack(CommandStack &stack)
Chris@376 456 {
Chris@376 457 while (!stack.empty()) {
Chris@376 458 Command *command = stack.top();
Chris@376 459 // Not safe to call getName() on a command about to be deleted
Chris@377 460 #ifdef DEBUG_COMMAND_HISTORY
Chris@587 461 SVDEBUG << "CommandHistory::clearStack: About to delete command " << command << endl;
Chris@377 462 #endif
Chris@376 463 delete command;
Chris@376 464 stack.pop();
Chris@376 465 }
Chris@376 466 }
Chris@376 467
Chris@376 468 void
Chris@376 469 CommandHistory::undoActivated(QAction *action)
Chris@376 470 {
Chris@376 471 int pos = m_actionCounts[action];
Chris@376 472 for (int i = 0; i <= pos; ++i) {
Chris@376 473 undo();
Chris@376 474 }
Chris@376 475 }
Chris@376 476
Chris@376 477 void
Chris@376 478 CommandHistory::redoActivated(QAction *action)
Chris@376 479 {
Chris@376 480 int pos = m_actionCounts[action];
Chris@376 481 for (int i = 0; i <= pos; ++i) {
Chris@376 482 redo();
Chris@376 483 }
Chris@376 484 }
Chris@376 485
Chris@376 486 void
Chris@376 487 CommandHistory::updateActions()
Chris@376 488 {
Chris@376 489 m_actionCounts.clear();
Chris@376 490
Chris@376 491 for (int undo = 0; undo <= 1; ++undo) {
Chris@376 492
Chris@376 493 QAction *action(undo ? m_undoAction : m_redoAction);
Chris@376 494 QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction);
Chris@376 495 QMenu *menu(undo ? m_undoMenu : m_redoMenu);
Chris@376 496 CommandStack &stack(undo ? m_undoStack : m_redoStack);
Chris@376 497
Chris@376 498 if (stack.empty()) {
Chris@376 499
Chris@376 500 QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo"));
Chris@376 501
Chris@376 502 action->setEnabled(false);
Chris@376 503 action->setText(text);
Chris@376 504
Chris@376 505 menuAction->setEnabled(false);
Chris@376 506 menuAction->setText(text);
Chris@376 507
Chris@376 508 } else {
Chris@376 509
Chris@376 510 action->setEnabled(true);
Chris@376 511 menuAction->setEnabled(true);
Chris@376 512
Chris@376 513 QString commandName = stack.top()->getName();
Chris@376 514 commandName.replace(QRegExp("&"), "");
Chris@376 515
Chris@376 516 QString text = (undo ? tr("&Undo %1") : tr("Re&do %1"))
Chris@376 517 .arg(commandName);
Chris@376 518
Chris@376 519 action->setText(text);
Chris@376 520 menuAction->setText(text);
Chris@376 521 }
Chris@376 522
Chris@376 523 menu->clear();
Chris@376 524
Chris@376 525 CommandStack tempStack;
Chris@376 526 int j = 0;
Chris@376 527
Chris@376 528 while (j < m_menuLimit && !stack.empty()) {
Chris@376 529
Chris@376 530 Command *command = stack.top();
Chris@376 531 tempStack.push(command);
Chris@376 532 stack.pop();
Chris@376 533
Chris@376 534 QString commandName = command->getName();
Chris@376 535 commandName.replace(QRegExp("&"), "");
Chris@376 536
Chris@376 537 QString text;
Chris@376 538 if (undo) text = tr("&Undo %1").arg(commandName);
Chris@376 539 else text = tr("Re&do %1").arg(commandName);
Chris@376 540
Chris@376 541 QAction *action = menu->addAction(text);
Chris@376 542 m_actionCounts[action] = j++;
Chris@376 543 }
Chris@376 544
Chris@376 545 while (!tempStack.empty()) {
Chris@376 546 stack.push(tempStack.top());
Chris@376 547 tempStack.pop();
Chris@376 548 }
Chris@376 549 }
Chris@376 550 }
Chris@376 551