KDE3Support
k3command.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE project 00002 Copyright (C) 2000 Werner Trobin <trobin@kde.org> 00003 Copyright (C) 2000,2006 David Faure <faure@kde.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "k3command.h" 00022 #include <kaction.h> 00023 #include <kactioncollection.h> 00024 #include <kstandardshortcut.h> 00025 #include <kstandardaction.h> 00026 #include <kdebug.h> 00027 #include <kicon.h> 00028 #include <klocale.h> 00029 #include <kmenu.h> 00030 00031 #include "ktoolbarpopupaction.h" 00032 00033 K3Command::K3Command() 00034 : d( 0 ) 00035 { 00036 } 00037 00038 K3Command::~K3Command() 00039 { 00040 } 00041 00042 class K3NamedCommand::Private 00043 { 00044 public: 00045 QString name; 00046 }; 00047 00048 K3NamedCommand::K3NamedCommand( const QString &name ) 00049 : K3Command(), 00050 d( new Private ) 00051 { 00052 d->name = name; 00053 } 00054 00055 K3NamedCommand::~K3NamedCommand() 00056 { 00057 delete d; 00058 } 00059 00060 QString K3NamedCommand::name() const 00061 { 00062 return d->name; 00063 } 00064 00065 void K3NamedCommand::setName( const QString &name ) 00066 { 00067 d->name = name; 00068 } 00069 00070 class K3MacroCommand::Private 00071 { 00072 public: 00073 QList<K3Command *> commands; 00074 }; 00075 00076 K3MacroCommand::K3MacroCommand( const QString & name ) 00077 : K3NamedCommand(name), 00078 d( new Private ) 00079 { 00080 } 00081 00082 K3MacroCommand::~K3MacroCommand() 00083 { 00084 qDeleteAll( d->commands ); 00085 } 00086 00087 void K3MacroCommand::addCommand( K3Command *command ) 00088 { 00089 d->commands.append(command); 00090 } 00091 00092 void K3MacroCommand::execute() 00093 { 00094 QListIterator<K3Command *> it( d->commands ); 00095 while ( it.hasNext() ) { 00096 it.next()->execute(); 00097 } 00098 } 00099 00100 void K3MacroCommand::unexecute() 00101 { 00102 QListIterator<K3Command *> it( d->commands ); 00103 it.toBack(); 00104 while ( it.hasPrevious() ) { 00105 it.previous()->unexecute(); 00106 } 00107 } 00108 00109 const QList<K3Command *> K3MacroCommand::commands() const 00110 { 00111 return d->commands; 00112 } 00113 00114 00115 class K3CommandHistory::K3CommandHistoryPrivate { 00116 public: 00117 K3CommandHistoryPrivate() 00118 : m_undoLimit(50), m_redoLimit(30), 00119 m_savedAt(-1), m_current(-1) { 00120 } 00121 ~K3CommandHistoryPrivate() { 00122 qDeleteAll( m_commands ); 00123 } 00124 00125 QList<K3Command *> m_commands; 00126 int m_undoLimit, m_redoLimit; 00127 00128 int m_savedAt; 00129 int m_current; 00130 /* 00131 If m_commands contains: <c0> <c1> <c2> <c3> 00132 00133 m_current = 1 means we are between <c1> and <c2>, i.e. undo would unexecute c1. 00134 So m_current is the index of the current undo command, m_current+1 the current redo command if any. 00135 00136 Adding a command at this point would delete <c2> and <c3>. 00137 m_current compared to the commands: -1 <c0> 0 <c1> 1 <c2> 2. 00138 00139 m_savedAt = 1 means that we where at m_current == 1 when the document was saved. 00140 m_savedAt = -1 means that the document was saved with an empty history (initial state, too). 00141 m_savedAt = -2 means that the document wasn't saved in the current visible history 00142 (this happens when the undo history got truncated) 00143 */ 00144 }; 00145 00147 00148 K3CommandHistory::K3CommandHistory() : 00149 d( new K3CommandHistoryPrivate ) 00150 { 00151 clear(); 00152 } 00153 00154 K3CommandHistory::K3CommandHistory(KActionCollection * actionCollection, bool withMenus) : 00155 d( new K3CommandHistoryPrivate ) 00156 { 00157 if (withMenus) 00158 { 00159 // TODO instead of a menu this should show a listbox like koffice's KoCommandHistory does, 00160 // so that it's clearer that N actions will be undone together, not just action number N. 00161 00162 // TODO also move this out of K3CommandHistory, to make it core-only. 00163 00164 new K3UndoRedoAction( K3UndoRedoAction::Undo, actionCollection, this ); 00165 new K3UndoRedoAction( K3UndoRedoAction::Redo, actionCollection, this ); 00166 } 00167 else 00168 { 00169 actionCollection->addAction(KStandardAction::Undo, this, SLOT(undo())); 00170 actionCollection->addAction(KStandardAction::Redo, this, SLOT(redo())); 00171 } 00172 clear(); 00173 } 00174 00175 K3CommandHistory::~K3CommandHistory() { 00176 delete d; 00177 } 00178 00179 void K3CommandHistory::clear() { 00180 qDeleteAll( d->m_commands ); 00181 d->m_commands.clear(); 00182 d->m_current = -1; 00183 d->m_savedAt = -1; 00184 emit commandHistoryChanged(); 00185 } 00186 00187 void K3CommandHistory::addCommand(K3Command *command, bool execute) { 00188 if ( !command ) 00189 return; 00190 00191 ++d->m_current; 00192 d->m_commands.insert( d->m_current, command ); 00193 // Truncate history 00194 int count = d->m_commands.count(); 00195 for ( int i = d->m_current + 1; i < count; ++i ) 00196 delete d->m_commands.takeLast(); 00197 00198 // Check whether we still can reach savedAt 00199 if ( d->m_current < d->m_savedAt ) 00200 d->m_savedAt = -2; 00201 00202 clipCommands(); 00203 00204 if ( execute ) 00205 { 00206 command->execute(); 00207 emit commandExecuted(command); 00208 } 00209 } 00210 00211 K3Command * K3CommandHistory::presentCommand() const 00212 { 00213 if ( d->m_current >= 0 ) 00214 return d->m_commands[ d->m_current ]; 00215 return 0; 00216 } 00217 00218 void K3CommandHistory::undo() { 00219 Q_ASSERT( d->m_current >= 0 ); 00220 00221 K3Command* command = d->m_commands[ d->m_current ]; 00222 00223 command->unexecute(); 00224 emit commandExecuted( command ); 00225 00226 --d->m_current; 00227 00228 if ( d->m_current == d->m_savedAt ) 00229 emit documentRestored(); 00230 00231 clipCommands(); // only needed here and in addCommand, NOT in redo 00232 } 00233 00234 void K3CommandHistory::redo() { 00235 K3Command* command = d->m_commands[ d->m_current + 1 ]; 00236 command->execute(); 00237 emit commandExecuted( command ); 00238 00239 ++d->m_current; 00240 00241 if ( d->m_current == d->m_savedAt ) 00242 emit documentRestored(); 00243 00244 emit commandHistoryChanged(); 00245 } 00246 00247 void K3CommandHistory::documentSaved() { 00248 d->m_savedAt = d->m_current; 00249 } 00250 00251 void K3CommandHistory::setUndoLimit(int limit) { 00252 if ( limit>0 && limit != d->m_undoLimit ) { 00253 d->m_undoLimit = limit; 00254 clipCommands(); 00255 } 00256 } 00257 00258 void K3CommandHistory::setRedoLimit(int limit) { 00259 if ( limit>0 && limit != d->m_redoLimit ) { 00260 d->m_redoLimit = limit; 00261 clipCommands(); 00262 } 00263 } 00264 00265 void K3CommandHistory::clipCommands() { 00266 int count = d->m_commands.count(); 00267 if ( count <= d->m_undoLimit && count <= d->m_redoLimit ) { 00268 emit commandHistoryChanged(); 00269 return; 00270 } 00271 00272 if ( d->m_current >= d->m_undoLimit ) { 00273 const int toRemove = (d->m_current - d->m_undoLimit) + 1; 00274 for ( int i = 0; i < toRemove; ++i ) { 00275 delete d->m_commands.takeFirst(); 00276 --d->m_savedAt; 00277 --d->m_current; 00278 } 00279 Q_ASSERT( d->m_current >= -1 ); 00280 count = d->m_commands.count(); // update count for the redo branch below 00281 if ( d->m_savedAt < 0 ) 00282 d->m_savedAt = -1; // savedAt went out of the history 00283 } 00284 00285 if ( d->m_current + d->m_redoLimit + 1 < count ) { 00286 if ( d->m_savedAt > (d->m_current + d->m_redoLimit) ) 00287 d->m_savedAt = -1; 00288 const int toRemove = count - (d->m_current + d->m_redoLimit + 1); 00289 for ( int i = 0; i< toRemove; ++i ) 00290 delete d->m_commands.takeLast(); 00291 } 00292 emit commandHistoryChanged(); 00293 } 00294 00295 void K3CommandHistory::updateActions() 00296 { 00297 // it hasn't changed, but this updates all actions connected to this command history. 00298 emit commandHistoryChanged(); 00299 } 00300 00301 bool K3CommandHistory::isUndoAvailable() const 00302 { 00303 return d->m_current >= 0; 00304 } 00305 00306 bool K3CommandHistory::isRedoAvailable() const 00307 { 00308 return d->m_current < d->m_commands.count() - 1; 00309 } 00310 00311 QList<K3Command *> K3CommandHistory::undoCommands( int maxCommands ) const 00312 { 00313 QList<K3Command *> lst; 00314 for ( int i = d->m_current; i >= 0; --i ) { 00315 lst.append( d->m_commands[i] ); 00316 if ( maxCommands > 0 && lst.count() == maxCommands ) 00317 break; 00318 } 00319 return lst; 00320 } 00321 00322 QList<K3Command *> K3CommandHistory::redoCommands( int maxCommands ) const 00323 { 00324 QList<K3Command *> lst; 00325 for ( int i = d->m_current + 1; i < d->m_commands.count(); ++i ) 00326 { 00327 lst.append( d->m_commands[i] ); 00328 if ( maxCommands > 0 && lst.count() == maxCommands ) 00329 break; 00330 } 00331 return lst; 00332 } 00333 00334 int K3CommandHistory::undoLimit() const 00335 { 00336 return d->m_undoLimit; 00337 } 00338 00339 int K3CommandHistory::redoLimit() const 00340 { 00341 return d->m_redoLimit; 00342 } 00343 00344 class K3UndoRedoAction::Private 00345 { 00346 public: 00347 Private( K3UndoRedoAction::Type type, K3CommandHistory* commandHistory) 00348 : type( type ), 00349 commandHistory( commandHistory ) 00350 { 00351 } 00352 00353 Type type; 00354 K3CommandHistory* commandHistory; 00355 }; 00356 00357 00358 00359 K3UndoRedoAction::K3UndoRedoAction( Type type, KActionCollection* actionCollection, K3CommandHistory* commandHistory ) 00360 : KToolBarPopupAction( KIcon( type == Undo ? "edit-undo" : "edit-redo" ), 00361 QString(), // text is set in clear() on start 00362 actionCollection), 00363 d( new Private( type, commandHistory ) ) 00364 { 00365 setShortcut( KStandardShortcut::shortcut( type == Undo ? KStandardShortcut::Undo : KStandardShortcut::Redo ) ); 00366 if ( d->type == Undo ) { 00367 connect( this, SIGNAL(triggered(bool)), d->commandHistory, SLOT(undo()) ); 00368 } else { 00369 connect( this, SIGNAL(triggered(bool)), d->commandHistory, SLOT(redo()) ); 00370 } 00371 connect( this->menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow()) ); 00372 connect( this->menu(), SIGNAL(triggered(QAction*)), this, SLOT(slotActionTriggered(QAction*)) ); 00373 00374 connect( d->commandHistory, SIGNAL(commandHistoryChanged()), this, SLOT(slotCommandHistoryChanged()) ); 00375 slotCommandHistoryChanged(); 00376 actionCollection->addAction(KStandardAction::name(type == Undo ? KStandardAction::Undo : KStandardAction::Redo), 00377 this); 00378 } 00379 00380 void K3UndoRedoAction::slotAboutToShow() 00381 { 00382 menu()->clear(); 00383 // TODO make number of items configurable ? 00384 const int maxCommands = 9; 00385 if ( d->type == Undo ) { 00386 const QList<K3Command *> commands = d->commandHistory->undoCommands( maxCommands ); 00387 for (int i = 0; i < commands.count(); ++i) { 00388 QAction *action = menu()->addAction( i18n("Undo: %1", commands[i]->name()) ); 00389 action->setData( i ); 00390 } 00391 } else { 00392 const QList<K3Command *> commands = d->commandHistory->redoCommands( maxCommands ); 00393 for (int i = 0; i < commands.count(); ++i) { 00394 QAction *action = menu()->addAction( i18n("Redo: %1", commands[i]->name()) ); 00395 action->setData( i ); 00396 } 00397 } 00398 } 00399 00400 void K3UndoRedoAction::slotActionTriggered( QAction *action ) 00401 { 00402 const int pos = action->data().toInt(); 00403 //kDebug(230) << pos; 00404 if ( d->type == Undo ) { 00405 for ( int i = 0 ; i < pos+1; ++i ) { 00406 d->commandHistory->undo(); 00407 } 00408 } else { 00409 for ( int i = 0 ; i < pos+1; ++i ) { 00410 d->commandHistory->redo(); 00411 } 00412 } 00413 } 00414 00415 void K3UndoRedoAction::slotCommandHistoryChanged() 00416 { 00417 const bool isUndo = d->type == Undo; 00418 const bool enabled = isUndo ? d->commandHistory->isUndoAvailable() : d->commandHistory->isRedoAvailable(); 00419 setEnabled(enabled); 00420 if (!enabled) { 00421 setText(isUndo ? i18n("&Undo") : i18n("&Redo")); 00422 } else { 00423 if (isUndo) { 00424 K3Command* presentCommand = d->commandHistory->presentCommand(); 00425 Q_ASSERT(presentCommand); 00426 setText(i18n("&Undo: %1", presentCommand->name())); 00427 } else { 00428 K3Command* redoCommand = d->commandHistory->redoCommands(1).first(); 00429 setText(i18n("&Redo: %1", redoCommand->name())); 00430 } 00431 } 00432 } 00433 00434 00435 void K3Command::virtual_hook( int, void* ) 00436 { /*BASE::virtual_hook( id, data );*/ } 00437 00438 void K3NamedCommand::virtual_hook( int id, void* data ) 00439 { K3Command::virtual_hook( id, data ); } 00440 00441 void K3MacroCommand::virtual_hook( int id, void* data ) 00442 { K3NamedCommand::virtual_hook( id, data ); } 00443 00444 #include "k3command.moc"
KDE 4.6 API Reference