annotate base/CommandHistory.cpp @ 46:5364a9d338a2

* Add Insert Instant function in main window * Ensure selections and window geometry are saved in session file * Add wait cursor on session file save * Various improvements to display of texts in pane (clearer readability) * Use commands for setting properties on layers and panes (still need to batch up multiple sets on the same property) * Fix failure of spectrogram to refresh when initial part became visible * Some fixes & paint optimisations in View &c * Make curve mode for time value layers work properly when resolution == 1 * Some vague improvements for time value layer vertical scale
author Chris Cannam
date Thu, 16 Mar 2006 18:46:00 +0000
parents 701404725897
children bac8b14ab355
rev   line source
Chris@16 1 /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */
Chris@16 2
Chris@16 3 /*
Chris@16 4 A waveform viewer and audio annotation editor.
Chris@16 5 Chris Cannam, Queen Mary University of London, 2005-2006
Chris@16 6
Chris@16 7 This is experimental software. Not for distribution.
Chris@16 8 */
Chris@16 9
Chris@16 10 /*
Chris@16 11 This is a modified version of a source file from the Rosegarden
Chris@16 12 MIDI and audio sequencer and notation editor, copyright 2000-2006
Chris@16 13 Chris Cannam, distributed under the GNU General Public License.
Chris@16 14
Chris@16 15 This file contains traces of the KCommandHistory class from the KDE
Chris@16 16 project, copyright 2000 Werner Trobin and David Faure and
Chris@16 17 distributed under the GNU Lesser General Public License.
Chris@16 18 */
Chris@16 19
Chris@17 20 #include "CommandHistory.h"
Chris@16 21
Chris@16 22 #include "Command.h"
Chris@16 23
Chris@16 24 #include <QRegExp>
Chris@16 25 #include <QMenu>
Chris@16 26 #include <QToolBar>
Chris@16 27 #include <QString>
Chris@16 28
Chris@16 29 #include <iostream>
Chris@16 30
Chris@17 31 CommandHistory *CommandHistory::m_instance = 0;
Chris@17 32
Chris@17 33 CommandHistory::CommandHistory() :
Chris@16 34 m_undoLimit(50),
Chris@16 35 m_redoLimit(50),
Chris@46 36 m_menuLimit(15),
Chris@44 37 m_savedAt(0),
Chris@44 38 m_currentMacro(0),
Chris@44 39 m_executeMacro(false)
Chris@16 40 {
Chris@16 41 m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
Chris@16 42 m_undoAction->setShortcut(tr("Ctrl+Z"));
Chris@16 43 connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo()));
Chris@16 44
Chris@17 45 m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
Chris@17 46 connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo()));
Chris@17 47
Chris@16 48 m_undoMenu = new QMenu(tr("&Undo"));
Chris@17 49 m_undoMenuAction->setMenu(m_undoMenu);
Chris@16 50 connect(m_undoMenu, SIGNAL(triggered(QAction *)),
Chris@16 51 this, SLOT(undoActivated(QAction*)));
Chris@16 52
Chris@17 53 m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
Chris@16 54 m_redoAction->setShortcut(tr("Ctrl+Shift+Z"));
Chris@16 55 connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo()));
Chris@17 56
Chris@17 57 m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
Chris@17 58 connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo()));
Chris@16 59
Chris@16 60 m_redoMenu = new QMenu(tr("Re&do"));
Chris@17 61 m_redoMenuAction->setMenu(m_redoMenu);
Chris@16 62 connect(m_redoMenu, SIGNAL(triggered(QAction *)),
Chris@16 63 this, SLOT(redoActivated(QAction*)));
Chris@16 64 }
Chris@16 65
Chris@17 66 CommandHistory::~CommandHistory()
Chris@16 67 {
Chris@16 68 m_savedAt = -1;
Chris@16 69 clearStack(m_undoStack);
Chris@16 70 clearStack(m_redoStack);
Chris@16 71
Chris@16 72 delete m_undoMenu;
Chris@16 73 delete m_redoMenu;
Chris@16 74 }
Chris@16 75
Chris@17 76 CommandHistory *
Chris@17 77 CommandHistory::getInstance()
Chris@17 78 {
Chris@17 79 if (!m_instance) m_instance = new CommandHistory();
Chris@17 80 return m_instance;
Chris@17 81 }
Chris@17 82
Chris@16 83 void
Chris@17 84 CommandHistory::clear()
Chris@16 85 {
Chris@16 86 m_savedAt = -1;
Chris@16 87 clearStack(m_undoStack);
Chris@16 88 clearStack(m_redoStack);
Chris@16 89 updateActions();
Chris@16 90 }
Chris@16 91
Chris@16 92 void
Chris@17 93 CommandHistory::registerMenu(QMenu *menu)
Chris@16 94 {
Chris@16 95 menu->addAction(m_undoAction);
Chris@16 96 menu->addAction(m_redoAction);
Chris@16 97 }
Chris@16 98
Chris@16 99 void
Chris@17 100 CommandHistory::registerToolbar(QToolBar *toolbar)
Chris@16 101 {
Chris@17 102 toolbar->addAction(m_undoMenuAction);
Chris@17 103 toolbar->addAction(m_redoMenuAction);
Chris@16 104 }
Chris@16 105
Chris@16 106 void
Chris@17 107 CommandHistory::addCommand(Command *command, bool execute)
Chris@16 108 {
Chris@16 109 if (!command) return;
Chris@16 110
Chris@44 111 if (m_currentMacro) {
Chris@44 112 addToMacro(command);
Chris@44 113 return;
Chris@44 114 }
Chris@44 115
Chris@46 116 std::cerr << "MVCH::addCommand: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
Chris@16 117
Chris@16 118 // We can't redo after adding a command
Chris@16 119 clearStack(m_redoStack);
Chris@16 120
Chris@16 121 // can we reach savedAt?
Chris@16 122 if ((int)m_undoStack.size() < m_savedAt) m_savedAt = -1; // nope
Chris@16 123
Chris@16 124 m_undoStack.push(command);
Chris@16 125 clipCommands();
Chris@16 126
Chris@16 127 if (execute) {
Chris@16 128 command->execute();
Chris@16 129 }
Chris@16 130
Chris@17 131 // Emit even if we aren't executing the command, because
Chris@17 132 // someone must have executed it for this to make any sense
Chris@17 133 emit commandExecuted();
Chris@17 134 emit commandExecuted(command);
Chris@17 135
Chris@16 136 updateActions();
Chris@16 137 }
Chris@16 138
Chris@16 139 void
Chris@44 140 CommandHistory::addToMacro(Command *command)
Chris@44 141 {
Chris@44 142 std::cerr << "MVCH::addToMacro: " << command->getName().toLocal8Bit().data() << std::endl;
Chris@44 143
Chris@44 144 if (m_executeMacro) command->execute();
Chris@44 145 m_currentMacro->addCommand(command);
Chris@44 146 }
Chris@44 147
Chris@44 148 void
Chris@44 149 CommandHistory::startCompoundOperation(QString name, bool execute)
Chris@44 150 {
Chris@44 151 if (m_currentMacro) {
Chris@44 152 std::cerr << "MVCH::startCompoundOperation: ERROR: compound operation already in progress!" << std::endl;
Chris@44 153 std::cerr << "(name is " << m_currentMacro->getName().toLocal8Bit().data() << ")" << std::endl;
Chris@44 154 }
Chris@44 155
Chris@44 156 m_currentMacro = new MacroCommand(name);
Chris@44 157 m_executeMacro = execute;
Chris@44 158 }
Chris@44 159
Chris@44 160 void
Chris@44 161 CommandHistory::endCompoundOperation()
Chris@44 162 {
Chris@44 163 if (!m_currentMacro) {
Chris@44 164 std::cerr << "MVCH::endCompoundOperation: ERROR: no compound operation in progress!" << std::endl;
Chris@44 165 }
Chris@44 166
Chris@44 167 Command *toAdd = m_currentMacro;
Chris@44 168 m_currentMacro = 0;
Chris@44 169
Chris@44 170 // We don't execute the macro command here, because we have been
Chris@44 171 // executing the individual commands as we went along if
Chris@44 172 // m_executeMacro was true.
Chris@44 173 addCommand(toAdd, false);
Chris@44 174 }
Chris@44 175
Chris@44 176 void
Chris@17 177 CommandHistory::addExecutedCommand(Command *command)
Chris@17 178 {
Chris@17 179 addCommand(command, false);
Chris@17 180 }
Chris@17 181
Chris@17 182 void
Chris@17 183 CommandHistory::addCommandAndExecute(Command *command)
Chris@17 184 {
Chris@17 185 addCommand(command, true);
Chris@17 186 }
Chris@17 187
Chris@17 188 void
Chris@17 189 CommandHistory::undo()
Chris@16 190 {
Chris@16 191 if (m_undoStack.empty()) return;
Chris@16 192
Chris@16 193 Command *command = m_undoStack.top();
Chris@16 194 command->unexecute();
Chris@16 195 emit commandExecuted();
Chris@17 196 emit commandUnexecuted(command);
Chris@16 197
Chris@16 198 m_redoStack.push(command);
Chris@16 199 m_undoStack.pop();
Chris@16 200
Chris@16 201 clipCommands();
Chris@16 202 updateActions();
Chris@16 203
Chris@16 204 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@16 205 }
Chris@16 206
Chris@16 207 void
Chris@17 208 CommandHistory::redo()
Chris@16 209 {
Chris@16 210 if (m_redoStack.empty()) return;
Chris@16 211
Chris@16 212 Command *command = m_redoStack.top();
Chris@16 213 command->execute();
Chris@16 214 emit commandExecuted();
Chris@16 215 emit commandExecuted(command);
Chris@16 216
Chris@16 217 m_undoStack.push(command);
Chris@16 218 m_redoStack.pop();
Chris@16 219 // no need to clip
Chris@16 220
Chris@16 221 updateActions();
Chris@41 222
Chris@41 223 if ((int)m_undoStack.size() == m_savedAt) emit documentRestored();
Chris@16 224 }
Chris@16 225
Chris@16 226 void
Chris@17 227 CommandHistory::setUndoLimit(int limit)
Chris@16 228 {
Chris@16 229 if (limit > 0 && limit != m_undoLimit) {
Chris@16 230 m_undoLimit = limit;
Chris@16 231 clipCommands();
Chris@16 232 }
Chris@16 233 }
Chris@16 234
Chris@16 235 void
Chris@17 236 CommandHistory::setRedoLimit(int limit)
Chris@16 237 {
Chris@16 238 if (limit > 0 && limit != m_redoLimit) {
Chris@16 239 m_redoLimit = limit;
Chris@16 240 clipCommands();
Chris@16 241 }
Chris@16 242 }
Chris@16 243
Chris@16 244 void
Chris@46 245 CommandHistory::setMenuLimit(int limit)
Chris@46 246 {
Chris@46 247 m_menuLimit = limit;
Chris@46 248 updateActions();
Chris@46 249 }
Chris@46 250
Chris@46 251 void
Chris@17 252 CommandHistory::documentSaved()
Chris@16 253 {
Chris@16 254 m_savedAt = m_undoStack.size();
Chris@16 255 }
Chris@16 256
Chris@16 257 void
Chris@17 258 CommandHistory::clipCommands()
Chris@16 259 {
Chris@16 260 if ((int)m_undoStack.size() > m_undoLimit) {
Chris@16 261 m_savedAt -= (m_undoStack.size() - m_undoLimit);
Chris@16 262 }
Chris@16 263
Chris@16 264 clipStack(m_undoStack, m_undoLimit);
Chris@16 265 clipStack(m_redoStack, m_redoLimit);
Chris@16 266 }
Chris@16 267
Chris@16 268 void
Chris@17 269 CommandHistory::clipStack(CommandStack &stack, int limit)
Chris@16 270 {
Chris@16 271 int i;
Chris@16 272
Chris@16 273 if ((int)stack.size() > limit) {
Chris@16 274
Chris@16 275 CommandStack tempStack;
Chris@16 276
Chris@16 277 for (i = 0; i < limit; ++i) {
Chris@16 278 Command *command = stack.top();
Chris@17 279 std::cerr << "MVCH::clipStack: Saving recent command: " << command->getName().toLocal8Bit().data() << " at " << command << std::endl;
Chris@16 280 tempStack.push(stack.top());
Chris@16 281 stack.pop();
Chris@16 282 }
Chris@16 283
Chris@16 284 clearStack(stack);
Chris@16 285
Chris@16 286 for (i = 0; i < m_undoLimit; ++i) {
Chris@16 287 stack.push(tempStack.top());
Chris@16 288 tempStack.pop();
Chris@16 289 }
Chris@16 290 }
Chris@16 291 }
Chris@16 292
Chris@16 293 void
Chris@17 294 CommandHistory::clearStack(CommandStack &stack)
Chris@16 295 {
Chris@16 296 while (!stack.empty()) {
Chris@16 297 Command *command = stack.top();
Chris@46 298 // Not safe to call getName() on a command about to be deleted
Chris@46 299 std::cerr << "MVCH::clearStack: About to delete command " << command << std::endl;
Chris@16 300 delete command;
Chris@16 301 stack.pop();
Chris@16 302 }
Chris@16 303 }
Chris@16 304
Chris@16 305 void
Chris@17 306 CommandHistory::undoActivated(QAction *action)
Chris@16 307 {
Chris@16 308 int pos = m_actionCounts[action];
Chris@16 309 for (int i = 0; i <= pos; ++i) {
Chris@16 310 undo();
Chris@16 311 }
Chris@16 312 }
Chris@16 313
Chris@16 314 void
Chris@17 315 CommandHistory::redoActivated(QAction *action)
Chris@16 316 {
Chris@16 317 int pos = m_actionCounts[action];
Chris@16 318 for (int i = 0; i <= pos; ++i) {
Chris@16 319 redo();
Chris@16 320 }
Chris@16 321 }
Chris@16 322
Chris@16 323 void
Chris@17 324 CommandHistory::updateActions()
Chris@16 325 {
Chris@16 326 m_actionCounts.clear();
Chris@16 327
Chris@16 328 for (int undo = 0; undo <= 1; ++undo) {
Chris@16 329
Chris@17 330 QAction *action(undo ? m_undoAction : m_redoAction);
Chris@17 331 QAction *menuAction(undo ? m_undoMenuAction : m_redoMenuAction);
Chris@16 332 QMenu *menu(undo ? m_undoMenu : m_redoMenu);
Chris@16 333 CommandStack &stack(undo ? m_undoStack : m_redoStack);
Chris@16 334
Chris@17 335 if (stack.empty()) {
Chris@17 336
Chris@17 337 QString text(undo ? tr("Nothing to undo") : tr("Nothing to redo"));
Chris@17 338
Chris@17 339 action->setEnabled(false);
Chris@17 340 action->setText(text);
Chris@17 341
Chris@17 342 menuAction->setEnabled(false);
Chris@17 343 menuAction->setText(text);
Chris@17 344
Chris@17 345 } else {
Chris@17 346
Chris@17 347 action->setEnabled(true);
Chris@17 348 menuAction->setEnabled(true);
Chris@17 349
Chris@17 350 QString commandName = stack.top()->getName();
Chris@17 351 commandName.replace(QRegExp("&"), "");
Chris@17 352
Chris@17 353 QString text = (undo ? tr("&Undo %1") : tr("Re&do %1"))
Chris@17 354 .arg(commandName);
Chris@17 355
Chris@17 356 action->setText(text);
Chris@17 357 menuAction->setText(text);
Chris@17 358 }
Chris@17 359
Chris@16 360 menu->clear();
Chris@16 361
Chris@16 362 CommandStack tempStack;
Chris@16 363 int j = 0;
Chris@16 364
Chris@46 365 while (j < m_menuLimit && !stack.empty()) {
Chris@16 366
Chris@16 367 Command *command = stack.top();
Chris@16 368 tempStack.push(command);
Chris@16 369 stack.pop();
Chris@16 370
Chris@17 371 QString commandName = command->getName();
Chris@16 372 commandName.replace(QRegExp("&"), "");
Chris@16 373
Chris@16 374 QString text;
Chris@16 375 if (undo) text = tr("&Undo %1").arg(commandName);
Chris@16 376 else text = tr("Re&do %1").arg(commandName);
Chris@16 377
Chris@16 378 QAction *action = menu->addAction(text);
Chris@16 379 m_actionCounts[action] = j++;
Chris@16 380 }
Chris@16 381
Chris@16 382 while (!tempStack.empty()) {
Chris@16 383 stack.push(tempStack.top());
Chris@16 384 tempStack.pop();
Chris@16 385 }
Chris@16 386 }
Chris@16 387 }
Chris@16 388