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 delete d; 00086 } 00087 00088 void K3MacroCommand::addCommand( K3Command *command ) 00089 { 00090 d->commands.append(command); 00091 } 00092 00093 void K3MacroCommand::execute() 00094 { 00095 QListIterator<K3Command *> it( d->commands ); 00096 while ( it.hasNext() ) { 00097 it.next()->execute(); 00098 } 00099 } 00100 00101 void K3MacroCommand::unexecute() 00102 { 00103 QListIterator<K3Command *> it( d->commands ); 00104 it.toBack(); 00105 while ( it.hasPrevious() ) { 00106 it.previous()->unexecute(); 00107 } 00108 } 00109 00110 const QList<K3Command *> K3MacroCommand::commands() const 00111 { 00112 return d->commands; 00113 } 00114 00115 00116 class K3CommandHistory::K3CommandHistoryPrivate { 00117 public: 00118 K3CommandHistoryPrivate() 00119 : m_undoLimit(50), m_redoLimit(30), 00120 m_savedAt(-1), m_current(-1) { 00121 } 00122 ~K3CommandHistoryPrivate() { 00123 qDeleteAll( m_commands ); 00124 } 00125 00126 QList<K3Command *> m_commands; 00127 int m_undoLimit, m_redoLimit; 00128 00129 int m_savedAt; 00130 int m_current; 00131 /* 00132 If m_commands contains: <c0> <c1> <c2> <c3> 00133 00134 m_current = 1 means we are between <c1> and <c2>, i.e. undo would unexecute c1. 00135 So m_current is the index of the current undo command, m_current+1 the current redo command if any. 00136 00137 Adding a command at this point would delete <c2> and <c3>. 00138 m_current compared to the commands: -1 <c0> 0 <c1> 1 <c2> 2. 00139 00140 m_savedAt = 1 means that we where at m_current == 1 when the document was saved. 00141 m_savedAt = -1 means that the document was saved with an empty history (initial state, too). 00142 m_savedAt = -2 means that the document wasn't saved in the current visible history 00143 (this happens when the undo history got truncated) 00144 */ 00145 }; 00146 00148 00149 K3CommandHistory::K3CommandHistory() : 00150 d( new K3CommandHistoryPrivate ) 00151 { 00152 clear(); 00153 } 00154 00155 K3CommandHistory::K3CommandHistory(KActionCollection * actionCollection, bool withMenus) : 00156 d( new K3CommandHistoryPrivate ) 00157 { 00158 if (withMenus) 00159 { 00160 // TODO instead of a menu this should show a listbox like koffice's KoCommandHistory does, 00161 // so that it's clearer that N actions will be undone together, not just action number N. 00162 00163 // TODO also move this out of K3CommandHistory, to make it core-only. 00164 00165 new K3UndoRedoAction( K3UndoRedoAction::Undo, actionCollection, this ); 00166 new K3UndoRedoAction( K3UndoRedoAction::Redo, actionCollection, this ); 00167 } 00168 else 00169 { 00170 actionCollection->addAction(KStandardAction::Undo, this, SLOT(undo())); 00171 actionCollection->addAction(KStandardAction::Redo, this, SLOT(redo())); 00172 } 00173 clear(); 00174 } 00175 00176 K3CommandHistory::~K3CommandHistory() { 00177 delete d; 00178 } 00179 00180 void K3CommandHistory::clear() { 00181 qDeleteAll( d->m_commands ); 00182 d->m_commands.clear(); 00183 d->m_current = -1; 00184 d->m_savedAt = -1; 00185 emit commandHistoryChanged(); 00186 } 00187 00188 void K3CommandHistory::addCommand(K3Command *command, bool execute) { 00189 if ( !command ) 00190 return; 00191 00192 ++d->m_current; 00193 d->m_commands.insert( d->m_current, command ); 00194 // Truncate history 00195 int count = d->m_commands.count(); 00196 for ( int i = d->m_current + 1; i < count; ++i ) 00197 delete d->m_commands.takeLast(); 00198 00199 // Check whether we still can reach savedAt 00200 if ( d->m_current < d->m_savedAt ) 00201 d->m_savedAt = -2; 00202 00203 clipCommands(); 00204 00205 if ( execute ) 00206 { 00207 command->execute(); 00208 emit commandExecuted(command); 00209 } 00210 } 00211 00212 K3Command * K3CommandHistory::presentCommand() const 00213 { 00214 if ( d->m_current >= 0 ) 00215 return d->m_commands[ d->m_current ]; 00216 return 0; 00217 } 00218 00219 void K3CommandHistory::undo() { 00220 Q_ASSERT( d->m_current >= 0 ); 00221 00222 K3Command* command = d->m_commands[ d->m_current ]; 00223 00224 command->unexecute(); 00225 emit commandExecuted( command ); 00226 00227 --d->m_current; 00228 00229 if ( d->m_current == d->m_savedAt ) 00230 emit documentRestored(); 00231 00232 clipCommands(); // only needed here and in addCommand, NOT in redo 00233 } 00234 00235 void K3CommandHistory::redo() { 00236 K3Command* command = d->m_commands[ d->m_current + 1 ]; 00237 command->execute(); 00238 emit commandExecuted( command ); 00239 00240 ++d->m_current; 00241 00242 if ( d->m_current == d->m_savedAt ) 00243 emit documentRestored(); 00244 00245 emit commandHistoryChanged(); 00246 } 00247 00248 void K3CommandHistory::documentSaved() { 00249 d->m_savedAt = d->m_current; 00250 } 00251 00252 void K3CommandHistory::setUndoLimit(int limit) { 00253 if ( limit>0 && limit != d->m_undoLimit ) { 00254 d->m_undoLimit = limit; 00255 clipCommands(); 00256 } 00257 } 00258 00259 void K3CommandHistory::setRedoLimit(int limit) { 00260 if ( limit>0 && limit != d->m_redoLimit ) { 00261 d->m_redoLimit = limit; 00262 clipCommands(); 00263 } 00264 } 00265 00266 void K3CommandHistory::clipCommands() { 00267 int count = d->m_commands.count(); 00268 if ( count <= d->m_undoLimit && count <= d->m_redoLimit ) { 00269 emit commandHistoryChanged(); 00270 return; 00271 } 00272 00273 if ( d->m_current >= d->m_undoLimit ) { 00274 const int toRemove = (d->m_current - d->m_undoLimit) + 1; 00275 for ( int i = 0; i < toRemove; ++i ) { 00276 delete d->m_commands.takeFirst(); 00277 --d->m_savedAt; 00278 --d->m_current; 00279 } 00280 Q_ASSERT( d->m_current >= -1 ); 00281 count = d->m_commands.count(); // update count for the redo branch below 00282 if ( d->m_savedAt < 0 ) 00283 d->m_savedAt = -1; // savedAt went out of the history 00284 } 00285 00286 if ( d->m_current + d->m_redoLimit + 1 < count ) { 00287 if ( d->m_savedAt > (d->m_current + d->m_redoLimit) ) 00288 d->m_savedAt = -1; 00289 const int toRemove = count - (d->m_current + d->m_redoLimit + 1); 00290 for ( int i = 0; i< toRemove; ++i ) 00291 delete d->m_commands.takeLast(); 00292 } 00293 emit commandHistoryChanged(); 00294 } 00295 00296 void K3CommandHistory::updateActions() 00297 { 00298 // it hasn't changed, but this updates all actions connected to this command history. 00299 emit commandHistoryChanged(); 00300 } 00301 00302 bool K3CommandHistory::isUndoAvailable() const 00303 { 00304 return d->m_current >= 0; 00305 } 00306 00307 bool K3CommandHistory::isRedoAvailable() const 00308 { 00309 return d->m_current < d->m_commands.count() - 1; 00310 } 00311 00312 QList<K3Command *> K3CommandHistory::undoCommands( int maxCommands ) const 00313 { 00314 QList<K3Command *> lst; 00315 for ( int i = d->m_current; i >= 0; --i ) { 00316 lst.append( d->m_commands[i] ); 00317 if ( maxCommands > 0 && lst.count() == maxCommands ) 00318 break; 00319 } 00320 return lst; 00321 } 00322 00323 QList<K3Command *> K3CommandHistory::redoCommands( int maxCommands ) const 00324 { 00325 QList<K3Command *> lst; 00326 for ( int i = d->m_current + 1; i < d->m_commands.count(); ++i ) 00327 { 00328 lst.append( d->m_commands[i] ); 00329 if ( maxCommands > 0 && lst.count() == maxCommands ) 00330 break; 00331 } 00332 return lst; 00333 } 00334 00335 int K3CommandHistory::undoLimit() const 00336 { 00337 return d->m_undoLimit; 00338 } 00339 00340 int K3CommandHistory::redoLimit() const 00341 { 00342 return d->m_redoLimit; 00343 } 00344 00345 class K3UndoRedoAction::Private 00346 { 00347 public: 00348 Private( K3UndoRedoAction::Type type, K3CommandHistory* commandHistory) 00349 : type( type ), 00350 commandHistory( commandHistory ) 00351 { 00352 } 00353 00354 Type type; 00355 K3CommandHistory* commandHistory; 00356 }; 00357 00358 00359 00360 K3UndoRedoAction::K3UndoRedoAction( Type type, KActionCollection* actionCollection, K3CommandHistory* commandHistory ) 00361 : KToolBarPopupAction( KIcon( type == Undo ? "edit-undo" : "edit-redo" ), 00362 QString(), // text is set in clear() on start 00363 actionCollection), 00364 d( new Private( type, commandHistory ) ) 00365 { 00366 setShortcut( KStandardShortcut::shortcut( type == Undo ? KStandardShortcut::Undo : KStandardShortcut::Redo ) ); 00367 if ( d->type == Undo ) { 00368 connect( this, SIGNAL(triggered(bool)), d->commandHistory, SLOT(undo()) ); 00369 } else { 00370 connect( this, SIGNAL(triggered(bool)), d->commandHistory, SLOT(redo()) ); 00371 } 00372 connect( this->menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow()) ); 00373 connect( this->menu(), SIGNAL(triggered(QAction*)), this, SLOT(slotActionTriggered(QAction*)) ); 00374 00375 connect( d->commandHistory, SIGNAL(commandHistoryChanged()), this, SLOT(slotCommandHistoryChanged()) ); 00376 slotCommandHistoryChanged(); 00377 actionCollection->addAction(KStandardAction::name(type == Undo ? KStandardAction::Undo : KStandardAction::Redo), 00378 this); 00379 } 00380 00381 void K3UndoRedoAction::slotAboutToShow() 00382 { 00383 menu()->clear(); 00384 // TODO make number of items configurable ? 00385 const int maxCommands = 9; 00386 if ( d->type == Undo ) { 00387 const QList<K3Command *> commands = d->commandHistory->undoCommands( maxCommands ); 00388 for (int i = 0; i < commands.count(); ++i) { 00389 QAction *action = menu()->addAction( i18n("Undo: %1", commands[i]->name()) ); 00390 action->setData( i ); 00391 } 00392 } else { 00393 const QList<K3Command *> commands = d->commandHistory->redoCommands( maxCommands ); 00394 for (int i = 0; i < commands.count(); ++i) { 00395 QAction *action = menu()->addAction( i18n("Redo: %1", commands[i]->name()) ); 00396 action->setData( i ); 00397 } 00398 } 00399 } 00400 00401 void K3UndoRedoAction::slotActionTriggered( QAction *action ) 00402 { 00403 const int pos = action->data().toInt(); 00404 //kDebug(230) << pos; 00405 if ( d->type == Undo ) { 00406 for ( int i = 0 ; i < pos+1; ++i ) { 00407 d->commandHistory->undo(); 00408 } 00409 } else { 00410 for ( int i = 0 ; i < pos+1; ++i ) { 00411 d->commandHistory->redo(); 00412 } 00413 } 00414 } 00415 00416 void K3UndoRedoAction::slotCommandHistoryChanged() 00417 { 00418 const bool isUndo = d->type == Undo; 00419 const bool enabled = isUndo ? d->commandHistory->isUndoAvailable() : d->commandHistory->isRedoAvailable(); 00420 setEnabled(enabled); 00421 if (!enabled) { 00422 setText(isUndo ? i18n("&Undo") : i18n("&Redo")); 00423 } else { 00424 if (isUndo) { 00425 K3Command* presentCommand = d->commandHistory->presentCommand(); 00426 Q_ASSERT(presentCommand); 00427 setText(i18n("&Undo: %1", presentCommand->name())); 00428 } else { 00429 K3Command* redoCommand = d->commandHistory->redoCommands(1).first(); 00430 setText(i18n("&Redo: %1", redoCommand->name())); 00431 } 00432 } 00433 } 00434 00435 00436 void K3Command::virtual_hook( int, void* ) 00437 { /*BASE::virtual_hook( id, data );*/ } 00438 00439 void K3NamedCommand::virtual_hook( int id, void* data ) 00440 { K3Command::virtual_hook( id, data ); } 00441 00442 void K3MacroCommand::virtual_hook( int id, void* data ) 00443 { K3NamedCommand::virtual_hook( id, data ); } 00444 00445 #include "k3command.moc"
KDE 4.7 API Reference