kcommand.cpp
00001 /* This file is part of the KDE project 00002 Copyright (C) 2000 Werner Trobin <trobin@kde.org> 00003 Copyright (C) 2000 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 "kcommand.h" 00022 #include <kaction.h> 00023 #include <kstdaccel.h> 00024 #include <kstdaction.h> 00025 #include <kdebug.h> 00026 #include <klocale.h> 00027 #include <kpopupmenu.h> 00028 00029 KCommand::~KCommand() 00030 { 00031 } 00032 00033 KMacroCommand::KMacroCommand( const QString & name ) : KNamedCommand(name) 00034 { 00035 m_commands.setAutoDelete(true); 00036 } 00037 00038 void KMacroCommand::addCommand(KCommand *command) 00039 { 00040 m_commands.append(command); 00041 } 00042 00043 void KMacroCommand::execute() 00044 { 00045 QPtrListIterator<KCommand> it(m_commands); 00046 for ( ; it.current() ; ++it ) 00047 it.current()->execute(); 00048 } 00049 00050 void KMacroCommand::unexecute() 00051 { 00052 QPtrListIterator<KCommand> it(m_commands); 00053 it.toLast(); 00054 for ( ; it.current() ; --it ) 00055 it.current()->unexecute(); 00056 } 00057 00058 00059 class KCommandHistory::KCommandHistoryPrivate { 00060 public: 00061 KCommandHistoryPrivate() { 00062 m_savedAt=-1; 00063 m_present=0; 00064 } 00065 ~KCommandHistoryPrivate() {} 00066 int m_savedAt; 00067 KCommand *m_present; 00068 }; 00069 00071 00072 KCommandHistory::KCommandHistory() : 00073 m_undo(0), m_redo(0), m_undoLimit(50), m_redoLimit(30), m_first(false) 00074 { 00075 d=new KCommandHistoryPrivate(); 00076 m_commands.setAutoDelete(true); 00077 clear(); 00078 } 00079 00080 KCommandHistory::KCommandHistory(KActionCollection * actionCollection, bool withMenus) : 00081 m_undoLimit(50), m_redoLimit(30), m_first(false) 00082 { 00083 d=new KCommandHistoryPrivate(); 00084 if (withMenus) 00085 { 00086 KToolBarPopupAction * undo = new KToolBarPopupAction( i18n("&Undo"), "undo", 00087 KStdAccel::shortcut(KStdAccel::Undo), this, SLOT( undo() ), 00088 actionCollection, KStdAction::stdName( KStdAction::Undo ) ); 00089 connect( undo->popupMenu(), SIGNAL( aboutToShow() ), this, SLOT( slotUndoAboutToShow() ) ); 00090 connect( undo->popupMenu(), SIGNAL( activated( int ) ), this, SLOT( slotUndoActivated( int ) ) ); 00091 m_undo = undo; 00092 m_undoPopup = undo->popupMenu(); 00093 00094 KToolBarPopupAction * redo = new KToolBarPopupAction( i18n("&Redo"), "redo", 00095 KStdAccel::shortcut(KStdAccel::Redo), this, SLOT( redo() ), 00096 actionCollection, KStdAction::stdName( KStdAction::Redo ) ); 00097 connect( redo->popupMenu(), SIGNAL( aboutToShow() ), this, SLOT( slotRedoAboutToShow() ) ); 00098 connect( redo->popupMenu(), SIGNAL( activated( int ) ), this, SLOT( slotRedoActivated( int ) ) ); 00099 m_redo = redo; 00100 m_redoPopup = redo->popupMenu(); 00101 } 00102 else 00103 { 00104 m_undo = KStdAction::undo( this, SLOT( undo() ), actionCollection ); 00105 m_redo = KStdAction::redo( this, SLOT( redo() ), actionCollection ); 00106 m_undoPopup = 0L; 00107 m_redoPopup = 0L; 00108 } 00109 m_commands.setAutoDelete(true); 00110 clear(); 00111 } 00112 00113 KCommandHistory::~KCommandHistory() { 00114 delete d; 00115 } 00116 00117 void KCommandHistory::clear() { 00118 if (m_undo) { 00119 m_undo->setEnabled(false); 00120 m_undo->setText(i18n("&Undo")); 00121 } 00122 if (m_redo) { 00123 m_redo->setEnabled(false); 00124 m_redo->setText(i18n("&Redo")); 00125 } 00126 d->m_present = 0L; 00127 d->m_savedAt=-42; 00128 } 00129 00130 void KCommandHistory::addCommand(KCommand *command, bool execute) { 00131 00132 if(!command) 00133 return; 00134 00135 int index; 00136 if(d->m_present && (index=m_commands.findRef(d->m_present))!=-1) { 00137 if (m_first) 00138 --index; 00139 m_commands.insert(index+1, command); 00140 // truncate history 00141 unsigned int count=m_commands.count(); 00142 for(unsigned int i=index+2; i<count; ++i) 00143 m_commands.removeLast(); 00144 // check whether we still can reach savedAt 00145 if(index<d->m_savedAt) 00146 d->m_savedAt=-1; 00147 d->m_present=command; 00148 m_first=false; 00149 if (m_undo) { 00150 m_undo->setEnabled(true); 00151 m_undo->setText(i18n("&Undo: %1").arg(d->m_present->name())); 00152 } 00153 if((m_redo) && m_redo->isEnabled()) { 00154 m_redo->setEnabled(false); 00155 m_redo->setText(i18n("&Redo")); 00156 } 00157 clipCommands(); 00158 } 00159 else { // either this is the first time we add a Command or something has gone wrong 00160 kdDebug(230) << "Initializing the Command History" << endl; 00161 m_commands.clear(); 00162 m_commands.append(command); 00163 d->m_present=command; 00164 if (m_undo) { 00165 m_undo->setEnabled(true); 00166 m_undo->setText(i18n("&Undo: %1").arg(d->m_present->name())); 00167 } 00168 if (m_redo) { 00169 m_redo->setEnabled(false); 00170 m_redo->setText(i18n("&Redo")); 00171 } 00172 m_first=false; // Michael B: yes, that *is* correct :-) 00173 } 00174 if ( execute ) 00175 { 00176 command->execute(); 00177 emit commandExecuted(); 00178 emit commandExecuted(command); 00179 } 00180 } 00181 00182 void KCommandHistory::undo() { 00183 00184 if (m_first || !d->m_present) 00185 return; 00186 00187 d->m_present->unexecute(); 00188 emit commandExecuted(); 00189 emit commandExecuted(d->m_present); 00190 if (m_redo) { 00191 m_redo->setEnabled(true); 00192 m_redo->setText(i18n("&Redo: %1").arg(d->m_present->name())); 00193 } 00194 int index; 00195 if((index=m_commands.findRef(d->m_present))!=-1 && m_commands.prev()) { 00196 d->m_present=m_commands.current(); 00197 if (m_undo) { 00198 m_undo->setEnabled(true); 00199 m_undo->setText(i18n("&Undo: %1").arg(d->m_present->name())); 00200 } 00201 --index; 00202 if(index==d->m_savedAt) 00203 emit documentRestored(); 00204 } 00205 else { 00206 if (m_undo) { 00207 m_undo->setEnabled(false); 00208 m_undo->setText(i18n("&Undo")); 00209 } 00210 if(d->m_savedAt==-42) 00211 emit documentRestored(); 00212 m_first=true; 00213 } 00214 clipCommands(); // only needed here and in addCommand, NOT in redo 00215 } 00216 00217 void KCommandHistory::redo() { 00218 00219 int index; 00220 if(m_first) { 00221 d->m_present->execute(); 00222 emit commandExecuted(); 00223 emit commandExecuted(d->m_present); 00224 m_first=false; 00225 m_commands.first(); 00226 if(!d->m_savedAt) 00227 emit documentRestored(); 00228 } 00229 else if((index=m_commands.findRef(d->m_present))!=-1 && m_commands.next()) { 00230 d->m_present=m_commands.current(); 00231 d->m_present->execute(); 00232 emit commandExecuted(); 00233 emit commandExecuted(d->m_present); 00234 ++index; 00235 if(index==d->m_savedAt) 00236 emit documentRestored(); 00237 } 00238 00239 if (m_undo) { 00240 m_undo->setEnabled(true); 00241 m_undo->setText(i18n("&Undo: %1").arg(d->m_present->name())); 00242 } 00243 00244 if(m_commands.next()) { 00245 if (m_redo) { 00246 m_redo->setEnabled(true); 00247 m_redo->setText(i18n("&Redo: %1").arg(m_commands.current()->name())); 00248 } 00249 } 00250 else { 00251 if((m_redo) && m_redo->isEnabled()) { 00252 m_redo->setEnabled(false); 00253 m_redo->setText(i18n("&Redo")); 00254 } 00255 } 00256 } 00257 00258 void KCommandHistory::documentSaved() { 00259 if(d->m_present && !m_first) 00260 d->m_savedAt=m_commands.findRef(d->m_present); 00261 else if(!d->m_present && !m_first) 00262 d->m_savedAt=-42; // this value signals that the document has 00263 // been saved with an empty history. 00264 else if(m_first) 00265 d->m_savedAt=-42; 00266 } 00267 00268 void KCommandHistory::setUndoLimit(int limit) { 00269 00270 if(limit>0 && limit!=m_undoLimit) { 00271 m_undoLimit=limit; 00272 clipCommands(); 00273 } 00274 } 00275 00276 void KCommandHistory::setRedoLimit(int limit) { 00277 00278 if(limit>0 && limit!=m_redoLimit) { 00279 m_redoLimit=limit; 00280 clipCommands(); 00281 } 00282 } 00283 00284 void KCommandHistory::clipCommands() { 00285 00286 int count=m_commands.count(); 00287 if(count<=m_undoLimit && count<=m_redoLimit) 00288 return; 00289 00290 int index=m_commands.findRef(d->m_present); 00291 if(index>=m_undoLimit) { 00292 for(int i=0; i<=(index-m_undoLimit); ++i) { 00293 m_commands.removeFirst(); 00294 --d->m_savedAt; 00295 if(d->m_savedAt==-1) 00296 d->m_savedAt=-42; 00297 } 00298 index=m_commands.findRef(d->m_present); // calculate the new 00299 count=m_commands.count(); // values (for the redo-branch :) 00300 // make it easier for us... d->m_savedAt==-1 -> invalid 00301 if(d->m_savedAt!=-42 && d->m_savedAt<-1) 00302 d->m_savedAt=-1; 00303 } 00304 // adjust the index if it's the first command 00305 if(m_first) 00306 index=-1; 00307 if((index+m_redoLimit+1)<count) { 00308 if(d->m_savedAt>(index+m_redoLimit)) 00309 d->m_savedAt=-1; 00310 for(int i=0; i<(count-(index+m_redoLimit+1)); ++i) 00311 m_commands.removeLast(); 00312 } 00313 } 00314 00315 void KCommandHistory::slotUndoAboutToShow() 00316 { 00317 m_undoPopup->clear(); 00318 int i = 0; 00319 if (m_commands.findRef(d->m_present)!=-1) 00320 while ( m_commands.current() && i<10 ) // TODO make number of items configurable ? 00321 { 00322 m_undoPopup->insertItem( i18n("Undo: %1").arg(m_commands.current()->name()), i++ ); 00323 m_commands.prev(); 00324 } 00325 } 00326 00327 void KCommandHistory::slotUndoActivated( int pos ) 00328 { 00329 kdDebug(230) << "KCommandHistory::slotUndoActivated " << pos << endl; 00330 for ( int i = 0 ; i < pos+1; ++i ) 00331 undo(); 00332 } 00333 00334 void KCommandHistory::slotRedoAboutToShow() 00335 { 00336 m_redoPopup->clear(); 00337 int i = 0; 00338 if (m_first) 00339 { 00340 d->m_present = m_commands.first(); 00341 m_redoPopup->insertItem( i18n("Redo: %1").arg(d->m_present->name()), i++ ); 00342 } 00343 if (m_commands.findRef(d->m_present)!=-1 && m_commands.next()) 00344 while ( m_commands.current() && i<10 ) // TODO make number of items configurable ? 00345 { 00346 m_redoPopup->insertItem( i18n("Redo: %1").arg(m_commands.current()->name()), i++ ); 00347 m_commands.next(); 00348 } 00349 } 00350 00351 void KCommandHistory::slotRedoActivated( int pos ) 00352 { 00353 kdDebug(230) << "KCommandHistory::slotRedoActivated " << pos << endl; 00354 for ( int i = 0 ; i < pos+1; ++i ) 00355 redo(); 00356 } 00357 00358 void KCommandHistory::updateActions() 00359 { 00360 if ( m_undo && m_redo ) 00361 { 00362 m_undo->setEnabled( !m_first && ( d->m_present ) ); 00363 m_redo->setEnabled(m_first || (m_commands.findRef(d->m_present)!=-1 && m_commands.next())); 00364 } 00365 } 00366 00367 void KCommand::virtual_hook( int, void* ) 00368 { /*BASE::virtual_hook( id, data );*/ } 00369 00370 void KNamedCommand::virtual_hook( int id, void* data ) 00371 { KCommand::virtual_hook( id, data ); } 00372 00373 void KMacroCommand::virtual_hook( int id, void* data ) 00374 { KNamedCommand::virtual_hook( id, data ); } 00375 00376 void KCommandHistory::virtual_hook( int, void* ) 00377 { /*BASE::virtual_hook( id, data );*/ } 00378 00379 #include "kcommand.moc"