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