konq_undo.cc
00001 /* This file is part of the KDE project 00002 Copyright (C) 2000 Simon Hausmann <hausmann@kde.org> 00003 00004 This library is free software; you can redistribute it and/or 00005 modify it under the terms of the GNU Library General Public 00006 License as published by the Free Software Foundation; either 00007 version 2 of the License, or (at your option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #include "konq_undo.h" 00021 00022 #undef Always 00023 00024 #include <tdeio/uiserver_stub.h> 00025 #include "konq_operations.h" 00026 00027 #include <assert.h> 00028 00029 #include <dcopclient.h> 00030 #include <dcopref.h> 00031 00032 #include <tdeapplication.h> 00033 #include <kdatastream.h> 00034 #include <kdebug.h> 00035 #include <tdelocale.h> 00036 #include <tdeglobalsettings.h> 00037 #include <tdeconfig.h> 00038 #include <kipc.h> 00039 00040 #include <tdeio/job.h> 00041 #include <kdirnotify_stub.h> 00042 00043 inline const char *dcopTypeName( const KonqCommand & ) { return "KonqCommand"; } 00044 inline const char *dcopTypeName( const KonqCommand::Stack & ) { return "KonqCommand::Stack"; } 00045 00066 class KonqUndoJob : public TDEIO::Job 00067 { 00068 public: 00069 KonqUndoJob() : TDEIO::Job( true ) { KonqUndoManager::incRef(); }; 00070 virtual ~KonqUndoJob() { KonqUndoManager::decRef(); } 00071 00072 virtual void kill( bool q) { KonqUndoManager::self()->stopUndo( true ); TDEIO::Job::kill( q ); } 00073 }; 00074 00075 class KonqCommandRecorder::KonqCommandRecorderPrivate 00076 { 00077 public: 00078 KonqCommandRecorderPrivate() 00079 { 00080 } 00081 ~KonqCommandRecorderPrivate() 00082 { 00083 } 00084 00085 KonqCommand m_cmd; 00086 }; 00087 00088 KonqCommandRecorder::KonqCommandRecorder( KonqCommand::Type op, const KURL::List &src, const KURL &dst, TDEIO::Job *job ) 00089 : TQObject( job, "konqcmdrecorder" ) 00090 { 00091 d = new KonqCommandRecorderPrivate; 00092 d->m_cmd.m_type = op; 00093 d->m_cmd.m_valid = true; 00094 d->m_cmd.m_src = src; 00095 d->m_cmd.m_dst = dst; 00096 connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), 00097 this, TQT_SLOT( slotResult( TDEIO::Job * ) ) ); 00098 00099 if ( op != KonqCommand::MKDIR ) { 00100 connect( job, TQT_SIGNAL( copyingDone( TDEIO::Job *, const KURL &, const KURL &, bool, bool ) ), 00101 this, TQT_SLOT( slotCopyingDone( TDEIO::Job *, const KURL &, const KURL &, bool, bool ) ) ); 00102 connect( job, TQT_SIGNAL( copyingLinkDone( TDEIO::Job *, const KURL &, const TQString &, const KURL & ) ), 00103 this, TQT_SLOT( slotCopyingLinkDone( TDEIO::Job *, const KURL &, const TQString &, const KURL & ) ) ); 00104 } 00105 00106 KonqUndoManager::incRef(); 00107 } 00108 00109 KonqCommandRecorder::~KonqCommandRecorder() 00110 { 00111 KonqUndoManager::decRef(); 00112 delete d; 00113 } 00114 00115 void KonqCommandRecorder::slotResult( TDEIO::Job *job ) 00116 { 00117 if ( job->error() ) 00118 return; 00119 00120 KonqUndoManager::self()->addCommand( d->m_cmd ); 00121 } 00122 00123 void KonqCommandRecorder::slotCopyingDone( TDEIO::Job *job, const KURL &from, const KURL &to, bool directory, bool renamed ) 00124 { 00125 KonqBasicOperation op; 00126 op.m_valid = true; 00127 op.m_directory = directory; 00128 op.m_renamed = renamed; 00129 op.m_src = from; 00130 op.m_dst = to; 00131 op.m_link = false; 00132 00133 if ( d->m_cmd.m_type == KonqCommand::TRASH ) 00134 { 00135 Q_ASSERT( from.isLocalFile() ); 00136 Q_ASSERT( to.protocol() == "trash" ); 00137 TQMap<TQString, TQString> metaData = job->metaData(); 00138 TQMap<TQString, TQString>::ConstIterator it = metaData.find( "trashURL-" + from.path() ); 00139 if ( it != metaData.end() ) { 00140 // Update URL 00141 op.m_dst = it.data(); 00142 } 00143 } 00144 00145 d->m_cmd.m_opStack.prepend( op ); 00146 } 00147 00148 void KonqCommandRecorder::slotCopyingLinkDone( TDEIO::Job *, const KURL &from, const TQString &target, const KURL &to ) 00149 { 00150 KonqBasicOperation op; 00151 op.m_valid = true; 00152 op.m_directory = false; 00153 op.m_renamed = false; 00154 op.m_src = from; 00155 op.m_target = target; 00156 op.m_dst = to; 00157 op.m_link = true; 00158 d->m_cmd.m_opStack.prepend( op ); 00159 } 00160 00161 KonqUndoManager *KonqUndoManager::s_self = 0; 00162 unsigned long KonqUndoManager::s_refCnt = 0; 00163 00164 class KonqUndoManager::KonqUndoManagerPrivate 00165 { 00166 public: 00167 KonqUndoManagerPrivate() 00168 { 00169 m_uiserver = new UIServer_stub( "tdeio_uiserver", "UIServer" ); 00170 m_undoJob = 0; 00171 } 00172 ~KonqUndoManagerPrivate() 00173 { 00174 delete m_uiserver; 00175 } 00176 00177 bool m_syncronized; 00178 00179 KonqCommand::Stack m_commands; 00180 00181 KonqCommand m_current; 00182 TDEIO::Job *m_currentJob; 00183 UndoState m_undoState; 00184 TQValueStack<KURL> m_dirStack; 00185 TQValueStack<KURL> m_dirCleanupStack; 00186 TQValueStack<KURL> m_fileCleanupStack; 00187 TQValueList<KURL> m_dirsToUpdate; 00188 00189 bool m_lock; 00190 00191 UIServer_stub *m_uiserver; 00192 int m_uiserverJobId; 00193 00194 KonqUndoJob *m_undoJob; 00195 }; 00196 00197 KonqUndoManager::KonqUndoManager() 00198 : DCOPObject( "KonqUndoManager" ) 00199 { 00200 if ( !kapp->dcopClient()->isAttached() ) 00201 kapp->dcopClient()->attach(); 00202 00203 d = new KonqUndoManagerPrivate; 00204 d->m_syncronized = initializeFromKDesky(); 00205 d->m_lock = false; 00206 d->m_currentJob = 0; 00207 } 00208 00209 KonqUndoManager::~KonqUndoManager() 00210 { 00211 delete d; 00212 } 00213 00214 void KonqUndoManager::incRef() 00215 { 00216 s_refCnt++; 00217 } 00218 00219 void KonqUndoManager::decRef() 00220 { 00221 s_refCnt--; 00222 if ( s_refCnt == 0 && s_self ) 00223 { 00224 delete s_self; 00225 s_self = 0; 00226 } 00227 } 00228 00229 KonqUndoManager *KonqUndoManager::self() 00230 { 00231 if ( !s_self ) 00232 { 00233 if ( s_refCnt == 0 ) 00234 s_refCnt++; // someone forgot to call incRef 00235 s_self = new KonqUndoManager; 00236 } 00237 return s_self; 00238 } 00239 00240 void KonqUndoManager::addCommand( const KonqCommand &cmd ) 00241 { 00242 broadcastPush( cmd ); 00243 } 00244 00245 bool KonqUndoManager::undoAvailable() const 00246 { 00247 return ( d->m_commands.count() > 0 ) && !d->m_lock; 00248 } 00249 00250 TQString KonqUndoManager::undoText() const 00251 { 00252 if ( d->m_commands.count() == 0 ) 00253 return i18n( "Und&o" ); 00254 00255 KonqCommand::Type t = d->m_commands.top().m_type; 00256 if ( t == KonqCommand::COPY ) 00257 return i18n( "Und&o: Copy" ); 00258 else if ( t == KonqCommand::LINK ) 00259 return i18n( "Und&o: Link" ); 00260 else if ( t == KonqCommand::MOVE ) 00261 return i18n( "Und&o: Move" ); 00262 else if ( t == KonqCommand::TRASH ) 00263 return i18n( "Und&o: Trash" ); 00264 else if ( t == KonqCommand::MKDIR ) 00265 return i18n( "Und&o: Create Folder" ); 00266 else 00267 assert( false ); 00268 /* NOTREACHED */ 00269 return TQString::null; 00270 } 00271 00272 void KonqUndoManager::undo() 00273 { 00274 KonqCommand cmd = d->m_commands.top(); 00275 assert( cmd.m_valid ); 00276 00277 d->m_current = cmd; 00278 00279 TQValueList<KonqBasicOperation>& opStack = d->m_current.m_opStack; 00280 00281 // Let's first ask for confirmation if we need to delete any file (#99898) 00282 KURL::List fileCleanupStack; 00283 TQValueList<KonqBasicOperation>::Iterator it = opStack.begin(); 00284 for ( ; it != opStack.end() ; ++it ) { 00285 if ( !(*it).m_directory && !(*it).m_link && d->m_current.m_type == KonqCommand::COPY ) { 00286 fileCleanupStack.append( (*it).m_dst ); 00287 } 00288 } 00289 if ( !fileCleanupStack.isEmpty() ) { 00290 // Because undo can happen with an accidental Ctrl-Z, we want to always confirm. 00291 if ( !KonqOperations::askDeleteConfirmation( fileCleanupStack, KonqOperations::DEL, 00292 KonqOperations::FORCE_CONFIRMATION, 00293 0 /* TODO parent */ ) ) 00294 return; 00295 } 00296 00297 d->m_dirCleanupStack.clear(); 00298 d->m_dirStack.clear(); 00299 d->m_dirsToUpdate.clear(); 00300 00301 d->m_undoState = MOVINGFILES; 00302 00303 broadcastPop(); 00304 broadcastLock(); 00305 00306 it = opStack.begin(); 00307 TQValueList<KonqBasicOperation>::Iterator end = opStack.end(); 00308 while ( it != end ) 00309 { 00310 if ( (*it).m_directory && !(*it).m_renamed ) 00311 { 00312 d->m_dirStack.push( (*it).m_src ); 00313 d->m_dirCleanupStack.prepend( (*it).m_dst ); 00314 it = d->m_current.m_opStack.remove( it ); 00315 d->m_undoState = MAKINGDIRS; 00316 kdDebug(1203) << "KonqUndoManager::undo MAKINGDIRS" << endl; 00317 } 00318 else if ( (*it).m_link ) 00319 { 00320 if ( !d->m_fileCleanupStack.contains( (*it).m_dst ) ) 00321 d->m_fileCleanupStack.prepend( (*it).m_dst ); 00322 00323 if ( d->m_current.m_type != KonqCommand::MOVE ) 00324 it = d->m_current.m_opStack.remove( it ); 00325 else 00326 ++it; 00327 } 00328 else 00329 ++it; 00330 } 00331 00332 /* this shouldn't be necessary at all: 00333 * 1) the source list may contain files, we don't want to 00334 * create those as... directories 00335 * 2) all directories that need creation should already be in the 00336 * directory stack 00337 if ( d->m_undoState == MAKINGDIRS ) 00338 { 00339 KURL::List::ConstIterator it = d->m_current.m_src.begin(); 00340 KURL::List::ConstIterator end = d->m_current.m_src.end(); 00341 for (; it != end; ++it ) 00342 if ( !d->m_dirStack.contains( *it) ) 00343 d->m_dirStack.push( *it ); 00344 } 00345 */ 00346 00347 if ( d->m_current.m_type != KonqCommand::MOVE ) 00348 d->m_dirStack.clear(); 00349 00350 d->m_undoJob = new KonqUndoJob; 00351 d->m_uiserverJobId = d->m_undoJob->progressId(); 00352 undoStep(); 00353 } 00354 00355 void KonqUndoManager::stopUndo( bool step ) 00356 { 00357 d->m_current.m_opStack.clear(); 00358 d->m_dirCleanupStack.clear(); 00359 d->m_fileCleanupStack.clear(); 00360 d->m_undoState = REMOVINGDIRS; 00361 d->m_undoJob = 0; 00362 00363 if ( d->m_currentJob ) 00364 d->m_currentJob->kill( true ); 00365 00366 d->m_currentJob = 0; 00367 00368 if ( step ) 00369 undoStep(); 00370 } 00371 00372 void KonqUndoManager::slotResult( TDEIO::Job *job ) 00373 { 00374 d->m_uiserver->jobFinished( d->m_uiserverJobId ); 00375 if ( job->error() ) 00376 { 00377 job->showErrorDialog( 0L ); 00378 d->m_currentJob = 0; 00379 stopUndo( false ); 00380 if ( d->m_undoJob ) 00381 { 00382 delete d->m_undoJob; 00383 d->m_undoJob = 0; 00384 } 00385 } 00386 00387 undoStep(); 00388 } 00389 00390 00391 void KonqUndoManager::addDirToUpdate( const KURL& url ) 00392 { 00393 if ( d->m_dirsToUpdate.find( url ) == d->m_dirsToUpdate.end() ) 00394 d->m_dirsToUpdate.prepend( url ); 00395 } 00396 00397 void KonqUndoManager::undoStep() 00398 { 00399 d->m_currentJob = 0; 00400 00401 if ( d->m_undoState == MAKINGDIRS ) 00402 undoMakingDirectories(); 00403 00404 if ( d->m_undoState == MOVINGFILES ) 00405 undoMovingFiles(); 00406 00407 if ( d->m_undoState == REMOVINGFILES ) 00408 undoRemovingFiles(); 00409 00410 if ( d->m_undoState == REMOVINGDIRS ) 00411 undoRemovingDirectories(); 00412 00413 if ( d->m_currentJob ) 00414 connect( d->m_currentJob, TQT_SIGNAL( result( TDEIO::Job * ) ), 00415 this, TQT_SLOT( slotResult( TDEIO::Job * ) ) ); 00416 } 00417 00418 void KonqUndoManager::undoMakingDirectories() 00419 { 00420 if ( !d->m_dirStack.isEmpty() ) { 00421 KURL dir = d->m_dirStack.pop(); 00422 kdDebug(1203) << "KonqUndoManager::undoStep creatingDir " << dir.prettyURL() << endl; 00423 d->m_currentJob = TDEIO::mkdir( dir ); 00424 d->m_uiserver->creatingDir( d->m_uiserverJobId, dir ); 00425 } 00426 else 00427 d->m_undoState = MOVINGFILES; 00428 } 00429 00430 void KonqUndoManager::undoMovingFiles() 00431 { 00432 if ( !d->m_current.m_opStack.isEmpty() ) 00433 { 00434 KonqBasicOperation op = d->m_current.m_opStack.pop(); 00435 00436 assert( op.m_valid ); 00437 if ( op.m_directory ) 00438 { 00439 if ( op.m_renamed ) 00440 { 00441 kdDebug(1203) << "KonqUndoManager::undoStep rename " << op.m_dst.prettyURL() << " " << op.m_src.prettyURL() << endl; 00442 d->m_currentJob = TDEIO::rename( op.m_dst, op.m_src, false ); 00443 d->m_uiserver->moving( d->m_uiserverJobId, op.m_dst, op.m_src ); 00444 } 00445 else 00446 assert( 0 ); // this should not happen! 00447 } 00448 else if ( op.m_link ) 00449 { 00450 kdDebug(1203) << "KonqUndoManager::undoStep symlink " << op.m_target << " " << op.m_src.prettyURL() << endl; 00451 d->m_currentJob = TDEIO::symlink( op.m_target, op.m_src, true, false ); 00452 } 00453 else if ( d->m_current.m_type == KonqCommand::COPY ) 00454 { 00455 kdDebug(1203) << "KonqUndoManager::undoStep file_delete " << op.m_dst.prettyURL() << endl; 00456 d->m_currentJob = TDEIO::file_delete( op.m_dst ); 00457 d->m_uiserver->deleting( d->m_uiserverJobId, op.m_dst ); 00458 } 00459 else if ( d->m_current.m_type == KonqCommand::MOVE 00460 || d->m_current.m_type == KonqCommand::TRASH ) 00461 { 00462 kdDebug(1203) << "KonqUndoManager::undoStep file_move " << op.m_dst.prettyURL() << " " << op.m_src.prettyURL() << endl; 00463 d->m_currentJob = TDEIO::file_move( op.m_dst, op.m_src, -1, true ); 00464 d->m_uiserver->moving( d->m_uiserverJobId, op.m_dst, op.m_src ); 00465 } 00466 00467 // The above TDEIO jobs are lowlevel, they don't trigger KDirNotify notification 00468 // So we need to do it ourselves (but schedule it to the end of the undo, to compress them) 00469 KURL url( op.m_dst ); 00470 url.setPath( url.directory() ); 00471 addDirToUpdate( url ); 00472 00473 url = op.m_src; 00474 url.setPath( url.directory() ); 00475 addDirToUpdate( url ); 00476 } 00477 else 00478 d->m_undoState = REMOVINGFILES; 00479 } 00480 00481 void KonqUndoManager::undoRemovingFiles() 00482 { 00483 kdDebug(1203) << "KonqUndoManager::undoStep REMOVINGFILES" << endl; 00484 if ( !d->m_fileCleanupStack.isEmpty() ) 00485 { 00486 KURL file = d->m_fileCleanupStack.pop(); 00487 kdDebug(1203) << "KonqUndoManager::undoStep file_delete " << file.prettyURL() << endl; 00488 d->m_currentJob = TDEIO::file_delete( file ); 00489 d->m_uiserver->deleting( d->m_uiserverJobId, file ); 00490 00491 KURL url( file ); 00492 url.setPath( url.directory() ); 00493 addDirToUpdate( url ); 00494 } 00495 else 00496 { 00497 d->m_undoState = REMOVINGDIRS; 00498 00499 if ( d->m_dirCleanupStack.isEmpty() && d->m_current.m_type == KonqCommand::MKDIR ) 00500 d->m_dirCleanupStack << d->m_current.m_dst; 00501 } 00502 } 00503 00504 void KonqUndoManager::undoRemovingDirectories() 00505 { 00506 if ( !d->m_dirCleanupStack.isEmpty() ) 00507 { 00508 KURL dir = d->m_dirCleanupStack.pop(); 00509 kdDebug(1203) << "KonqUndoManager::undoStep rmdir " << dir.prettyURL() << endl; 00510 d->m_currentJob = TDEIO::rmdir( dir ); 00511 d->m_uiserver->deleting( d->m_uiserverJobId, dir ); 00512 addDirToUpdate( dir ); 00513 } 00514 else 00515 { 00516 d->m_current.m_valid = false; 00517 d->m_currentJob = 0; 00518 if ( d->m_undoJob ) 00519 { 00520 kdDebug(1203) << "KonqUndoManager::undoStep deleting undojob" << endl; 00521 d->m_uiserver->jobFinished( d->m_uiserverJobId ); 00522 delete d->m_undoJob; 00523 d->m_undoJob = 0; 00524 } 00525 KDirNotify_stub allDirNotify( "*", "KDirNotify*" ); 00526 TQValueList<KURL>::ConstIterator it = d->m_dirsToUpdate.begin(); 00527 for( ; it != d->m_dirsToUpdate.end(); ++it ) { 00528 kdDebug() << "Notifying FilesAdded for " << *it << endl; 00529 allDirNotify.FilesAdded( *it ); 00530 } 00531 broadcastUnlock(); 00532 } 00533 } 00534 00535 void KonqUndoManager::push( const KonqCommand &cmd ) 00536 { 00537 d->m_commands.push( cmd ); 00538 emit undoAvailable( true ); 00539 emit undoTextChanged( undoText() ); 00540 } 00541 00542 void KonqUndoManager::pop() 00543 { 00544 d->m_commands.pop(); 00545 emit undoAvailable( undoAvailable() ); 00546 emit undoTextChanged( undoText() ); 00547 } 00548 00549 void KonqUndoManager::lock() 00550 { 00551 // assert( !d->m_lock ); 00552 d->m_lock = true; 00553 emit undoAvailable( undoAvailable() ); 00554 } 00555 00556 void KonqUndoManager::unlock() 00557 { 00558 // assert( d->m_lock ); 00559 d->m_lock = false; 00560 emit undoAvailable( undoAvailable() ); 00561 } 00562 00563 KonqCommand::Stack KonqUndoManager::get() const 00564 { 00565 return d->m_commands; 00566 } 00567 00568 void KonqUndoManager::broadcastPush( const KonqCommand &cmd ) 00569 { 00570 if ( !d->m_syncronized ) 00571 { 00572 push( cmd ); 00573 return; 00574 } 00575 00576 DCOPRef( "kdesktop", "KonqUndoManager" ).send( "push", cmd ); 00577 DCOPRef( "konqueror*", "KonqUndoManager" ).send( "push", cmd ); 00578 } 00579 00580 void KonqUndoManager::broadcastPop() 00581 { 00582 if ( !d->m_syncronized ) 00583 { 00584 pop(); 00585 return; 00586 } 00587 DCOPRef( "kdesktop", "KonqUndoManager" ).send( "pop" ); 00588 DCOPRef( "konqueror*", "KonqUndoManager" ).send( "pop" ); 00589 } 00590 00591 void KonqUndoManager::broadcastLock() 00592 { 00593 // assert( !d->m_lock ); 00594 00595 if ( !d->m_syncronized ) 00596 { 00597 lock(); 00598 return; 00599 } 00600 DCOPRef( "kdesktop", "KonqUndoManager" ).send( "lock" ); 00601 DCOPRef( "konqueror*", "KonqUndoManager" ).send( "lock" ); 00602 } 00603 00604 void KonqUndoManager::broadcastUnlock() 00605 { 00606 // assert( d->m_lock ); 00607 00608 if ( !d->m_syncronized ) 00609 { 00610 unlock(); 00611 return; 00612 } 00613 DCOPRef( "kdesktop", "KonqUndoManager" ).send( "unlock" ); 00614 DCOPRef( "konqueror*", "KonqUndoManager" ).send( "unlock" ); 00615 } 00616 00617 bool KonqUndoManager::initializeFromKDesky() 00618 { 00619 // ### workaround for dcop problem and upcoming 2.1 release: 00620 // in case of huge io operations the amount of data sent over 00621 // dcop (containing undo information broadcasted for global undo 00622 // to all konqueror instances) can easily exceed the 64kb limit 00623 // of dcop. In order not to run into trouble we disable global 00624 // undo for now! (Simon) 00625 // ### FIXME: post 2.1 00626 return false; 00627 00628 DCOPClient *client = kapp->dcopClient(); 00629 00630 if ( client->appId() == "kdesktop" ) // we are master :) 00631 return true; 00632 00633 if ( !client->isApplicationRegistered( "kdesktop" ) ) 00634 return false; 00635 00636 d->m_commands = DCOPRef( "kdesktop", "KonqUndoManager" ).call( "get" ); 00637 return true; 00638 } 00639 00640 TQDataStream &operator<<( TQDataStream &stream, const KonqBasicOperation &op ) 00641 { 00642 stream << op.m_valid << op.m_directory << op.m_renamed << op.m_link 00643 << op.m_src << op.m_dst << op.m_target; 00644 return stream; 00645 } 00646 TQDataStream &operator>>( TQDataStream &stream, KonqBasicOperation &op ) 00647 { 00648 stream >> op.m_valid >> op.m_directory >> op.m_renamed >> op.m_link 00649 >> op.m_src >> op.m_dst >> op.m_target; 00650 return stream; 00651 } 00652 00653 TQDataStream &operator<<( TQDataStream &stream, const KonqCommand &cmd ) 00654 { 00655 stream << cmd.m_valid << (TQ_INT8)cmd.m_type << cmd.m_opStack << cmd.m_src << cmd.m_dst; 00656 return stream; 00657 } 00658 00659 TQDataStream &operator>>( TQDataStream &stream, KonqCommand &cmd ) 00660 { 00661 TQ_INT8 type; 00662 stream >> cmd.m_valid >> type >> cmd.m_opStack >> cmd.m_src >> cmd.m_dst; 00663 cmd.m_type = static_cast<KonqCommand::Type>( type ); 00664 return stream; 00665 } 00666 00667 #include "konq_undo.moc"