lib Library API Documentation

koFilterChain.cpp

00001 /* This file is part of the KOffice libraries
00002    Copyright (C) 2001 Werner Trobin <trobin@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 version 2 as published by the Free Software Foundation.
00007 
00008    This library is distributed in the hope that it will be useful,
00009    but WITHOUT ANY WARRANTY; without even the implied warranty of
00010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011    Library General Public License for more details.
00012 
00013    You should have received a copy of the GNU Library General Public License
00014    along with this library; see the file COPYING.LIB.  If not, write to
00015    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00016    Boston, MA 02111-1307, USA.
00017 */
00018 
00019 #include <qmetaobject.h>
00020 #include <ktempfile.h>
00021 #include <kmimetype.h>
00022 #include <koFilterChain.h>
00023 #include <koQueryTrader.h>
00024 #include <koFilterManager.h>  // KoFilterManager::filterAvailable, private API
00025 #include <koDocument.h>
00026 #include <kdebug.h>
00027 
00028 #include <priorityqueue.h>
00029 
00030 #include <limits.h> // UINT_MAX
00031 
00032 // Those "defines" are needed in the setupConnections method below.
00033 // Please always keep the strings and the length in sync!
00034 namespace {
00035     const char* const SIGNAL_PREFIX = "commSignal";
00036     const int SIGNAL_PREFIX_LEN = 10;
00037     const char* const SLOT_PREFIX = "commSlot";
00038     const int SLOT_PREFIX_LEN = 8;
00039 }
00040 
00041 
00042 KoFilterChain::ChainLink::ChainLink( KoFilterChain* chain, KoFilterEntry::Ptr filterEntry,
00043                                      const QCString& from, const QCString& to ) :
00044     m_chain( chain ), m_filterEntry( filterEntry ), m_from( from ), m_to( to ),
00045     m_filter( 0 ), d( 0 )
00046 {
00047 }
00048 
00049 KoFilter::ConversionStatus KoFilterChain::ChainLink::invokeFilter( const ChainLink* const parentChainLink )
00050 {
00051     if ( !m_filterEntry ) {
00052         kdError( 30500 ) << "This filter entry is null. Strange stuff going on." << endl;
00053         return KoFilter::CreationError;
00054     }
00055 
00056     m_filter = m_filterEntry->createFilter( m_chain, 0, 0 );
00057 
00058     if ( !m_filter ) {
00059         kdError( 30500 ) << "Couldn't create the filter." << endl;
00060         return KoFilter::CreationError;
00061     }
00062 
00063     if ( parentChainLink )
00064         setupCommunication( parentChainLink->m_filter );
00065 
00066     KoFilter::ConversionStatus status = m_filter->convert( m_from, m_to );
00067     delete m_filter;
00068     m_filter=0;
00069     return status;
00070 }
00071 
00072 void KoFilterChain::ChainLink::dump() const
00073 {
00074     kdDebug( 30500 ) << "   Link: " << m_filterEntry->service()->name() << endl;
00075 }
00076 
00077 int KoFilterChain::ChainLink::lruPartIndex() const
00078 {
00079     if ( m_filter && m_filter->inherits( "KoEmbeddingFilter" ) )
00080         return static_cast<KoEmbeddingFilter*>( m_filter )->lruPartIndex();
00081     return -1;
00082 }
00083 
00084 void KoFilterChain::ChainLink::setupCommunication( const KoFilter* const parentFilter ) const
00085 {
00086     // progress information
00087     QObject::connect( m_filter, SIGNAL( sigProgress( int ) ),
00088                       m_chain->manager(), SIGNAL( sigProgress( int ) ) );
00089 
00090     if ( !parentFilter )
00091         return;
00092 
00093     const QMetaObject* const parent = parentFilter->metaObject();
00094     const QMetaObject* const child = m_filter->metaObject();
00095     if ( !parent || !child )
00096         return;
00097 
00098     setupConnections( parentFilter, parent->signalNames(), m_filter, child->slotNames() );
00099     setupConnections( m_filter, child->signalNames(), parentFilter, parent->slotNames() );
00100 }
00101 
00102 void KoFilterChain::ChainLink::setupConnections( const KoFilter* sender, const QStrList& sigs,
00103                                                  const KoFilter* receiver, const QStrList& sl0ts ) const
00104 {
00105     QStrListIterator signalIt( sigs );
00106     for ( ; signalIt.current(); ++signalIt ) {
00107         if ( strncmp( signalIt.current(), SIGNAL_PREFIX, SIGNAL_PREFIX_LEN ) == 0 ) {
00108             QStrListIterator slotIt( sl0ts );
00109             for ( ; slotIt.current(); ++slotIt ) {
00110                 if ( strncmp( slotIt.current(), SLOT_PREFIX, SLOT_PREFIX_LEN ) == 0 ) {
00111                     if ( strcmp( signalIt.current() + SIGNAL_PREFIX_LEN, slotIt.current() + SLOT_PREFIX_LEN ) == 0 ) {
00112                         QCString signalString;
00113                         signalString.setNum( QSIGNAL_CODE );
00114                         signalString += signalIt.current();
00115                         QCString slotString;
00116                         slotString.setNum( QSLOT_CODE );
00117                         slotString += slotIt.current();
00118                         QObject::connect( sender, signalString, receiver, slotString );
00119                     }
00120                 }
00121             }
00122         }
00123     }
00124 }
00125 
00126 
00127 KoFilterChain::~KoFilterChain()
00128 {
00129     if ( filterManagerParentChain() && filterManagerParentChain()->m_outputStorage )
00130         filterManagerParentChain()->m_outputStorage->leaveDirectory();
00131     manageIO(); // Called for the 2nd time in a row -> clean up
00132 }
00133 
00134 KoFilter::ConversionStatus KoFilterChain::invokeChain()
00135 {
00136     KoFilter::ConversionStatus status = KoFilter::OK;
00137 
00138     m_state = Beginning;
00139     int count = m_chainLinks.count();
00140 
00141     // This is needed due to nasty Microsoft design
00142     const ChainLink* parentChainLink = 0;
00143     if ( filterManagerParentChain() )
00144         parentChainLink = filterManagerParentChain()->m_chainLinks.current();
00145 
00146     // No iterator here, as we need m_chainLinks.current() in outputDocument()
00147     m_chainLinks.first();
00148     for ( ; count > 1 && m_chainLinks.current() && status == KoFilter::OK;
00149           m_chainLinks.next(), --count ) {
00150         status = m_chainLinks.current()->invokeFilter( parentChainLink );
00151         m_state = Middle;
00152         manageIO();
00153     }
00154 
00155     if ( !m_chainLinks.current() ) {
00156         kdWarning( 30500 ) << "Huh?? Found a null pointer in the chain" << endl;
00157         return KoFilter::StupidError;
00158     }
00159 
00160     if ( status == KoFilter::OK ) {
00161         if ( m_state & Beginning )
00162             m_state |= End;
00163         else
00164             m_state = End;
00165         status = m_chainLinks.current()->invokeFilter( parentChainLink );
00166         manageIO();
00167     }
00168 
00169     m_state = Done;
00170     finalizeIO();
00171     return status;
00172 }
00173 
00174 QString KoFilterChain::chainOutput() const
00175 {
00176     if ( m_state == Done )
00177         return m_inputFile; // as we already called manageIO()
00178     return QString::null;
00179 }
00180 
00181 QString KoFilterChain::inputFile()
00182 {
00183     if ( m_inputQueried == File )
00184         return m_inputFile;
00185     else if ( m_inputQueried != Nil ) {
00186         kdWarning( 30500 ) << "You already asked for some different source." << endl;
00187         return QString::null;
00188     }
00189     m_inputQueried = File;
00190 
00191     if ( m_state & Beginning ) {
00192         if ( static_cast<KoFilterManager::Direction>( filterManagerDirection() ) ==
00193              KoFilterManager::Import )
00194             m_inputFile = filterManagerImportFile();
00195         else
00196             inputFileHelper( filterManagerKoDocument(), filterManagerImportFile() );
00197     }
00198     else
00199         if ( m_inputFile.isEmpty() )
00200             inputFileHelper( m_inputDocument, QString::null );
00201 
00202     return m_inputFile;
00203 }
00204 
00205 QString KoFilterChain::outputFile()
00206 {
00207     // sanity check: No embedded filter should ask for a plain file
00208     // ###### CHECK: This will break as soon as we support exporting embedding filters
00209     if ( filterManagerParentChain() )
00210         kdWarning( 30500 )<< "An embedded filter has to use storageFile()!" << endl;
00211 
00212     if ( m_outputQueried == File )
00213         return m_outputFile;
00214     else if ( m_outputQueried != Nil ) {
00215         kdWarning( 30500 ) << "You already asked for some different destination." << endl;
00216         return QString::null;
00217     }
00218     m_outputQueried = File;
00219 
00220     if ( m_state & End ) {
00221         if ( static_cast<KoFilterManager::Direction>( filterManagerDirection() ) ==
00222              KoFilterManager::Import )
00223             outputFileHelper( false );  // This (last) one gets deleted by the caller
00224         else
00225             m_outputFile = filterManagerExportFile();
00226     }
00227     else
00228         outputFileHelper( true );
00229 
00230     return m_outputFile;
00231 }
00232 
00233 KoStoreDevice* KoFilterChain::storageFile( const QString& name, KoStore::Mode mode )
00234 {
00235     // ###### CHECK: This works only for import filters. Do we want something like
00236     // that for export filters too?
00237     if ( m_outputQueried == Nil && mode == KoStore::Write && filterManagerParentChain() )
00238         return storageInitEmbedding( name );
00239 
00240     // Plain normal use case
00241     if ( m_inputQueried == Storage && mode == KoStore::Read &&
00242          m_inputStorage && m_inputStorage->mode() == KoStore::Read )
00243         return storageNewStreamHelper( &m_inputStorage, &m_inputStorageDevice, name );
00244     else if ( m_outputQueried == Storage && mode == KoStore::Write &&
00245               m_outputStorage && m_outputStorage->mode() == KoStore::Write )
00246         return storageNewStreamHelper( &m_outputStorage, &m_outputStorageDevice, name );
00247     else if ( m_inputQueried == Nil && mode == KoStore::Read )
00248         return storageHelper( inputFile(), name, KoStore::Read,
00249                               &m_inputStorage, &m_inputStorageDevice );
00250     else if ( m_outputQueried == Nil && mode == KoStore::Write )
00251         return storageHelper( outputFile(), name, KoStore::Write,
00252                               &m_outputStorage, &m_outputStorageDevice );
00253     else {
00254         kdWarning( 30500 ) << "Oooops, how did we get here? You already asked for a"
00255                            << " different source/destination?" << endl;
00256         return 0;
00257     }
00258 }
00259 
00260 KoDocument* KoFilterChain::inputDocument()
00261 {
00262     if ( m_inputQueried == Document )
00263         return m_inputDocument;
00264     else if ( m_inputQueried != Nil ) {
00265         kdWarning( 30500 ) << "You already asked for some different source." << endl;
00266         return 0;
00267     }
00268 
00269     if ( ( m_state & Beginning ) &&
00270          static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == KoFilterManager::Export &&
00271          filterManagerKoDocument() )
00272         m_inputDocument = filterManagerKoDocument();
00273     else if ( !m_inputDocument )
00274         m_inputDocument = createDocument( inputFile() );
00275 
00276     m_inputQueried = Document;
00277     return m_inputDocument;
00278 }
00279 
00280 KoDocument* KoFilterChain::outputDocument()
00281 {
00282     // sanity check: No embedded filter should ask for a document
00283     // ###### CHECK: This will break as soon as we support exporting embedding filters
00284     if ( filterManagerParentChain() ) {
00285         kdWarning( 30500 )<< "An embedded filter has to use storageFile()!" << endl;
00286         return 0;
00287     }
00288 
00289     if ( m_outputQueried == Document )
00290         return m_outputDocument;
00291     else if ( m_outputQueried != Nil ) {
00292         kdWarning( 30500 ) << "You already asked for some different destination." << endl;
00293         return 0;
00294     }
00295 
00296     if ( ( m_state & End ) &&
00297          static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == KoFilterManager::Import &&
00298          filterManagerKoDocument() )
00299         m_outputDocument = filterManagerKoDocument();
00300     else
00301         m_outputDocument = createDocument( m_chainLinks.current()->to() );
00302 
00303     m_outputQueried = Document;
00304     return m_outputDocument;
00305 }
00306 
00307 void KoFilterChain::dump() const
00308 {
00309     kdDebug( 30500 ) << "########## KoFilterChain with " << m_chainLinks.count() << " members:" << endl;
00310     QPtrListIterator<ChainLink> it( m_chainLinks );
00311     for ( ; it.current(); ++it )
00312         it.current()->dump();
00313     kdDebug( 30500 ) << "########## KoFilterChain (done) ##########" << endl;
00314 }
00315 
00316 KoFilterChain::KoFilterChain( const KoFilterManager* manager ) :
00317     m_manager( manager ), m_state( Beginning ), m_inputStorage( 0 ),
00318     m_inputStorageDevice( 0 ), m_outputStorage( 0 ), m_outputStorageDevice( 0 ),
00319     m_inputDocument( 0 ), m_outputDocument( 0 ), m_inputTempFile( 0 ),
00320     m_outputTempFile( 0 ), m_inputQueried( Nil ), m_outputQueried( Nil ), d( 0 )
00321 {
00322     // We "own" our chain links, the filter entries are implicitly shared
00323     m_chainLinks.setAutoDelete( true );
00324 }
00325 
00326 void KoFilterChain::appendChainLink( KoFilterEntry::Ptr filterEntry, const QCString& from, const QCString& to )
00327 {
00328     m_chainLinks.append( new ChainLink( this, filterEntry, from, to ) );
00329 }
00330 
00331 void KoFilterChain::prependChainLink( KoFilterEntry::Ptr filterEntry, const QCString& from, const QCString& to )
00332 {
00333     m_chainLinks.prepend( new ChainLink( this, filterEntry, from, to ) );
00334 }
00335 
00336 void KoFilterChain::enterDirectory( const QString& directory )
00337 {
00338     // Only a little bit of checking as we (have to :} ) trust KoEmbeddingFilter
00339     // If the output storage isn't initialized yet, we perform that step(s) on init.
00340     if ( m_outputStorage )
00341         m_outputStorage->enterDirectory( directory );
00342     m_internalEmbeddingDirectories.append( directory );
00343 }
00344 
00345 void KoFilterChain::leaveDirectory()
00346 {
00347     if ( m_outputStorage )
00348         m_outputStorage->leaveDirectory();
00349     if ( !m_internalEmbeddingDirectories.isEmpty() )
00350         m_internalEmbeddingDirectories.pop_back();
00351 }
00352 
00353 QString KoFilterChain::filterManagerImportFile() const
00354 {
00355     return m_manager->importFile();
00356 }
00357 
00358 QString KoFilterChain::filterManagerExportFile() const
00359 {
00360     return m_manager->exportFile();
00361 }
00362 
00363 KoDocument* KoFilterChain::filterManagerKoDocument() const
00364 {
00365     return m_manager->document();
00366 }
00367 
00368 int KoFilterChain::filterManagerDirection() const
00369 {
00370     return m_manager->direction();
00371 }
00372 
00373 KoFilterChain* const KoFilterChain::filterManagerParentChain() const
00374 {
00375     return m_manager->parentChain();
00376 }
00377 
00378 void KoFilterChain::manageIO()
00379 {
00380     m_inputQueried = Nil;
00381     m_outputQueried = Nil;
00382 
00383     delete m_inputStorageDevice;
00384     m_inputStorageDevice = 0;
00385     if ( m_inputStorage ) {
00386         m_inputStorage->close();
00387         delete m_inputStorage;
00388         m_inputStorage = 0;
00389     }
00390     if ( m_inputTempFile ) {
00391         m_inputTempFile->close();
00392         delete m_inputTempFile;  // autodelete
00393         m_inputTempFile = 0;
00394     }
00395     m_inputFile = QString::null;
00396 
00397     if ( !m_outputFile.isEmpty() ) {
00398         m_inputFile = m_outputFile;
00399         m_outputFile = QString::null;
00400         m_inputTempFile = m_outputTempFile;
00401         m_outputTempFile = 0;
00402 
00403         delete m_outputStorageDevice;
00404         m_outputStorageDevice = 0;
00405         if ( m_outputStorage ) {
00406             m_outputStorage->close();
00407             // Don't delete the storage if we're just pointing to the
00408             // storage of the parent filter chain
00409             if ( !filterManagerParentChain() || m_outputStorage->mode() != KoStore::Write )
00410                 delete m_outputStorage;
00411             m_outputStorage = 0;
00412         }
00413     }
00414 
00415     if ( m_inputDocument != filterManagerKoDocument() )
00416         delete m_inputDocument;
00417     m_inputDocument = m_outputDocument;
00418     m_outputDocument = 0;
00419 }
00420 
00421 void KoFilterChain::finalizeIO()
00422 {
00423     // In case we export (to a file, of course) and the last
00424     // filter chose to output a KoDocument we have to save it.
00425     // Should be very rare, but well...
00426     // Note: m_*input*Document as we already called manageIO()
00427     if ( m_inputDocument &&
00428          static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == KoFilterManager::Export ) {
00429         kdDebug( 30500 ) << "Saving the output document to the export file" << endl;
00430         m_inputDocument->saveNativeFormat( filterManagerExportFile() );
00431         m_inputFile = filterManagerExportFile();
00432     }
00433 }
00434 
00435 bool KoFilterChain::createTempFile( KTempFile** tempFile, bool autoDelete )
00436 {
00437     if ( *tempFile ) {
00438         kdError( 30500 ) << "Ooops, why is there already a temp file???" << endl;
00439         return false;
00440     }
00441     *tempFile = new KTempFile();
00442     ( *tempFile )->setAutoDelete( autoDelete );
00443     return ( *tempFile )->status() == 0;
00444 }
00445 
00446 void KoFilterChain::inputFileHelper( KoDocument* document, const QString& alternativeFile )
00447 {
00448     if ( document ) {
00449         if ( !createTempFile( &m_inputTempFile ) ) {
00450             delete m_inputTempFile;
00451             m_inputTempFile = 0;
00452             m_inputFile = QString::null;
00453             return;
00454         }
00455         if ( !document->saveNativeFormat( m_inputTempFile->name() ) ) {
00456             delete m_inputTempFile;
00457             m_inputTempFile = 0;
00458             m_inputFile = QString::null;
00459             return;
00460         }
00461         m_inputFile = m_inputTempFile->name();
00462     }
00463     else
00464         m_inputFile = alternativeFile;
00465 }
00466 
00467 void KoFilterChain::outputFileHelper( bool autoDelete )
00468 {
00469     if ( !createTempFile( &m_outputTempFile, autoDelete ) ) {
00470         delete m_outputTempFile;
00471         m_outputTempFile = 0;
00472         m_outputFile = QString::null;
00473     }
00474     else
00475         m_outputFile = m_outputTempFile->name();
00476 }
00477 
00478 KoStoreDevice* KoFilterChain::storageNewStreamHelper( KoStore** storage, KoStoreDevice** device,
00479                                                       const QString& name )
00480 {
00481     delete *device;
00482     *device = 0;
00483     if ( ( *storage )->isOpen() )
00484         ( *storage )->close();
00485     if ( ( *storage )->bad() )
00486         return storageCleanupHelper( storage );
00487     if ( !( *storage )->open( name ) )
00488         return 0;
00489 
00490     *device = new KoStoreDevice( *storage );
00491     return *device;
00492 }
00493 
00494 KoStoreDevice* KoFilterChain::storageHelper( const QString& file, const QString& streamName,
00495                                              KoStore::Mode mode, KoStore** storage,
00496                                              KoStoreDevice** device )
00497 {
00498     if ( file.isEmpty() )
00499         return 0;
00500     if ( *storage ) {
00501         kdDebug( 30500 ) << "Uh-oh, we forgot to clean up..." << endl;
00502         return 0;
00503     }
00504 
00505     storageInit( file, mode, storage );
00506 
00507     if ( ( *storage )->bad() )
00508         return storageCleanupHelper( storage );
00509 
00510     // Seems that we got a valid storage, at least. Even if we can't open
00511     // the stream the "user" asked us to open, we nontheless change the
00512     // IOState from File to Storage, as it might be possible to open other streams
00513     if ( mode == KoStore::Read )
00514         m_inputQueried = Storage;
00515     else // KoStore::Write
00516         m_outputQueried = Storage;
00517 
00518     return storageCreateFirstStream( streamName, storage, device );
00519 }
00520 
00521 void KoFilterChain::storageInit( const QString& file, KoStore::Mode mode, KoStore** storage )
00522 {
00523     QCString appIdentification( "" );
00524     if ( mode == KoStore::Write ) {
00525         // To create valid storages we also have to add the mimetype
00526         // magic "applicationIndentifier" to the storage.
00527         // As only filters with a KOffice destination should query
00528         // for a storage to write to, we don't check the content of
00529         // the mimetype here. It doesn't do a lot of harm if someome
00530         // "abuses" this method.
00531         appIdentification = m_chainLinks.current()->to();
00532     }
00533     *storage = KoStore::createStore( file, mode, appIdentification );
00534 }
00535 
00536 KoStoreDevice* KoFilterChain::storageInitEmbedding( const QString& name )
00537 {
00538     if ( m_outputStorage ) {
00539         kdWarning( 30500 ) << "Ooops! Something's really screwed here." << endl;
00540         return 0;
00541     }
00542 
00543     m_outputStorage = filterManagerParentChain()->m_outputStorage;
00544 
00545     if ( !m_outputStorage ) {
00546         // If the storage of the parent hasn't been initialized yet,
00547         // we have to do that here. Quite nasty...
00548         storageInit( filterManagerParentChain()->outputFile(), KoStore::Write, &m_outputStorage );
00549 
00550         // transfer the ownership
00551         filterManagerParentChain()->m_outputStorage = m_outputStorage;
00552         filterManagerParentChain()->m_outputQueried = Storage;
00553     }
00554 
00555     if ( m_outputStorage->isOpen() )
00556         m_outputStorage->close();  // to be on the safe side, should never happen
00557     if ( m_outputStorage->bad() )
00558         return storageCleanupHelper( &m_outputStorage );
00559 
00560     m_outputQueried = Storage;
00561 
00562     // Now that we have a storage we have to change the directory
00563     // and remember it for later!
00564     const int lruPartIndex = filterManagerParentChain()->m_chainLinks.current()->lruPartIndex();
00565     if ( lruPartIndex == -1 ) {
00566         kdError( 30500 ) << "Huh! You want to use embedding features w/o inheriting KoEmbeddingFilter?" << endl;
00567         return storageCleanupHelper( &m_outputStorage );
00568     }
00569 
00570     if ( !m_outputStorage->enterDirectory( QString( "part%1" ).arg( lruPartIndex ) ) )
00571         return storageCleanupHelper( &m_outputStorage );
00572 
00573     return storageCreateFirstStream( name, &m_outputStorage, &m_outputStorageDevice );
00574 }
00575 
00576 KoStoreDevice* KoFilterChain::storageCreateFirstStream( const QString& streamName, KoStore** storage,
00577                                                         KoStoreDevice** device )
00578 {
00579     // Before we go and create the first stream in this storage we
00580     // have to perform a little hack in case we're used by any ole-style
00581     // filter which utilizes internal embedding. Ugly, but well...
00582     if ( !m_internalEmbeddingDirectories.isEmpty() ) {
00583         QStringList::ConstIterator it = m_internalEmbeddingDirectories.begin();
00584         QStringList::ConstIterator end = m_internalEmbeddingDirectories.end();
00585         for ( ; it != end && ( *storage )->enterDirectory( *it ); ++it );
00586     }
00587 
00588     if ( !( *storage )->open( streamName ) )
00589         return 0;
00590 
00591     if ( *device ) {
00592         kdDebug( 30500 ) << "Uh-oh, we forgot to clean up the storage device!" << endl;
00593         ( *storage )->close();
00594         return storageCleanupHelper( storage );
00595     }
00596     *device = new KoStoreDevice( *storage );
00597     return *device;
00598 }
00599 
00600 KoStoreDevice* KoFilterChain::storageCleanupHelper( KoStore** storage )
00601 {
00602     // Take care not to delete the storage of the parent chain
00603     if ( *storage != m_outputStorage || !filterManagerParentChain() ||
00604          ( *storage )->mode() != KoStore::Write )
00605         delete *storage;
00606     *storage = 0;
00607     return 0;
00608 }
00609 
00610 KoDocument* KoFilterChain::createDocument( const QString& file )
00611 {
00612     KURL url;
00613     url.setPath( file );
00614     KMimeType::Ptr t = KMimeType::findByURL( url, 0, true );
00615     if ( t->name() == KMimeType::defaultMimeType() ) {
00616         kdError( 30500 ) << "No mimetype found for " << file << endl;
00617         return 0;
00618     }
00619 
00620     KoDocument *doc = createDocument( QCString( t->name().latin1() ) );
00621 
00622     if ( !doc || !doc->loadNativeFormat( file ) ) {
00623         kdError( 30500 ) << "Couldn't load from the file" << endl;
00624         delete doc;
00625         return 0;
00626     }
00627     return doc;
00628 }
00629 
00630 KoDocument* KoFilterChain::createDocument( const QCString& mimeType )
00631 {
00632     const QString constraint( QString::fromLatin1( "[X-KDE-NativeMimeType] == '%1'" ).arg( mimeType ) );
00633     QValueList<KoDocumentEntry> entries = KoDocumentEntry::query( constraint );
00634     if ( entries.isEmpty() ) {
00635         kdError( 30500 ) << "Couldn't find a KOffice document entry for " << mimeType << endl;
00636         return 0;
00637     }
00638 
00639     if ( entries.count() != 1 )
00640         kdWarning( 30500 ) << "Huh?? Two document entries for the same mimetype?"
00641                            << " Will take the first one." << endl;
00642 
00643     KoDocument* doc = entries.first().createDoc();
00644     if ( !doc ) {
00645         kdError( 30500 ) << "Couldn't create the document" << endl;
00646         return 0;
00647     }
00648     return doc;
00649 }
00650 
00651 
00652 namespace KOffice {
00653 
00654     Edge::Edge( Vertex* vertex, KoFilterEntry::Ptr filterEntry ) :
00655         m_vertex( vertex ), m_filterEntry( filterEntry ), d( 0 )
00656     {
00657     }
00658 
00659     void Edge::relax( const Vertex* predecessor, PriorityQueue<Vertex>& queue )
00660     {
00661         if ( !m_vertex || !predecessor || !m_filterEntry )
00662             return;
00663         if ( m_vertex->setKey( predecessor->key() + m_filterEntry->weight ) ) {
00664             queue.keyDecreased( m_vertex ); // maintain the heap property
00665             m_vertex->setPredecessor( predecessor );
00666         }
00667     }
00668 
00669     void Edge::dump( const QCString& indent ) const
00670     {
00671         if ( m_vertex )
00672             kdDebug( 30500 ) << indent << "Edge -> '" << m_vertex->mimeType()
00673                              << "' (" << m_filterEntry->weight << ")" << endl;
00674         else
00675             kdDebug( 30500 ) << indent << "Edge -> '(null)' ("
00676                              << m_filterEntry->weight << ")" << endl;
00677     }
00678 
00679 
00680     Vertex::Vertex( const QCString& mimeType ) : m_predecessor( 0 ), m_mimeType( mimeType ),
00681         m_weight( UINT_MAX ), m_index( -1 ), d( 0 )
00682     {
00683         m_edges.setAutoDelete( true );  // we take ownership of added edges
00684     }
00685 
00686     bool Vertex::setKey( unsigned int key )
00687     {
00688         if ( m_weight > key ) {
00689             m_weight = key;
00690             return true;
00691         }
00692         return false;
00693     }
00694 
00695     void Vertex::reset()
00696     {
00697         m_weight = UINT_MAX;
00698         m_predecessor = 0;
00699     }
00700 
00701     void Vertex::addEdge( const Edge* edge )
00702     {
00703         if ( !edge || edge->weight() == 0 )
00704             return;
00705         m_edges.append( edge );
00706     }
00707 
00708     const Edge* Vertex::findEdge( const Vertex* vertex ) const
00709     {
00710         if ( !vertex )
00711             return 0;
00712         const Edge* edge = 0;
00713         QPtrListIterator<Edge> it( m_edges );
00714 
00715         for ( ; it.current(); ++it ) {
00716             if ( it.current()->vertex() == vertex &&
00717                  ( !edge || it.current()->weight() < edge->weight() ) )
00718                 edge = it.current();
00719         }
00720         return edge;
00721     }
00722 
00723     void Vertex::relaxVertices( PriorityQueue<Vertex>& queue )
00724     {
00725         for ( Edge *e = m_edges.first(); e; e = m_edges.next() )
00726             e->relax( this, queue );
00727     }
00728 
00729     void Vertex::dump( const QCString& indent ) const
00730     {
00731         kdDebug( 30500 ) << indent << "Vertex: " << m_mimeType << " (" << m_weight << "):" << endl;
00732         const QCString i( indent + "   " );
00733         QPtrListIterator<Edge> it( m_edges );
00734         for ( ; it.current(); ++it )
00735             it.current()->dump( i );
00736     }
00737 
00738 
00739     Graph::Graph( const QCString& from ) : m_vertices( 47 ), m_from( from ),
00740                                            m_graphValid( false ), d( 0 )
00741     {
00742         m_vertices.setAutoDelete( true );
00743         buildGraph();
00744         shortestPaths();  // Will return after a single lookup if "from" is invalid (->no check here)
00745     }
00746 
00747     void Graph::setSourceMimeType( const QCString& from )
00748     {
00749         if ( from == m_from )
00750             return;
00751         m_from = from;
00752         m_graphValid = false;
00753 
00754         // Initialize with "infinity" ...
00755         QAsciiDictIterator<Vertex> it( m_vertices );
00756         for ( ; it.current(); ++it )
00757             it.current()->reset();
00758 
00759         // ...and re-run the shortest path search for the new source mime
00760         shortestPaths();
00761     }
00762 
00763     KoFilterChain::Ptr Graph::chain( const KoFilterManager* manager, QCString& to ) const
00764     {
00765         if ( !isValid() || !manager )
00766             return 0;
00767 
00768         if ( to.isEmpty() ) {  // if the destination is empty we search the closest KOffice part
00769             to = findKOfficePart();
00770             if ( to.isEmpty() )  // still empty? strange stuff...
00771                 return 0;
00772         }
00773 
00774         const Vertex* vertex = m_vertices[ to ];
00775         if ( !vertex || vertex->key() == UINT_MAX )
00776             return 0;
00777 
00778         KoFilterChain::Ptr ret = new KoFilterChain( manager );
00779 
00780         // Fill the filter chain with all filters on the path
00781         const Vertex* tmp = vertex->predecessor();
00782         while ( tmp ) {
00783             const Edge* const edge = tmp->findEdge( vertex );
00784             Q_ASSERT( edge );
00785             ret->prependChainLink( edge->filterEntry(), tmp->mimeType(), vertex->mimeType() );
00786             vertex = tmp;
00787             tmp = tmp->predecessor();
00788         }
00789         return ret;
00790     }
00791 
00792     void Graph::dump() const
00793     {
00794         kdDebug( 30500 ) << "+++++++++ Graph::dump +++++++++" << endl;
00795         kdDebug( 30500 ) << "From: " << m_from << endl;
00796         QAsciiDictIterator<Vertex> it( m_vertices );
00797         for ( ; it.current(); ++it )
00798             it.current()->dump( "   " );
00799         kdDebug( 30500 ) << "+++++++++ Graph::dump (done) +++++++++" << endl;
00800     }
00801 
00802     // Query the trader and create the vertices and edges representing
00803     // available mime types and filters.
00804     void Graph::buildGraph()
00805     {
00806         // Make sure that all available parts are added to the graph
00807         QValueList<KoDocumentEntry> parts( KoDocumentEntry::query() );
00808         QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
00809         QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );
00810 
00811         while ( partIt != partEnd ) {
00812             const QCString key( ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString().latin1() );
00813             if ( !key.isEmpty() )
00814                 m_vertices.insert( key, new Vertex( key ) );
00815             ++partIt;
00816         }
00817 
00818         QValueList<KoFilterEntry::Ptr> filters( KoFilterEntry::query() ); // no constraint here - we want *all* :)
00819         QValueList<KoFilterEntry::Ptr>::ConstIterator it = filters.begin();
00820         QValueList<KoFilterEntry::Ptr>::ConstIterator end = filters.end();
00821 
00822         for ( ; it != end; ++it ) {
00823             // First add the "starting points" to the dict
00824             QStringList::ConstIterator importIt = ( *it )->import.begin();
00825             QStringList::ConstIterator importEnd = ( *it )->import.end();
00826             for ( ; importIt != importEnd; ++importIt ) {
00827                 const QCString key = ( *importIt ).latin1();  // latin1 is okay here (werner)
00828                 // already there?
00829                 if ( !m_vertices[ key ] )
00830                     m_vertices.insert( key, new Vertex( key ) );
00831             }
00832 
00833             // Are we allowed to use this filter at all?
00834             if ( KoFilterManager::filterAvailable( *it ) ) {
00835                 QStringList::ConstIterator exportIt = ( *it )->export_.begin();
00836                 QStringList::ConstIterator exportEnd = ( *it )->export_.end();
00837 
00838                 for ( ; exportIt != exportEnd; ++exportIt ) {
00839                     // First make sure the export vertex is in place
00840                     const QCString key = ( *exportIt ).latin1();  // latin1 is okay here
00841                     Vertex* exp = m_vertices[ key ];
00842                     if ( !exp ) {
00843                         exp = new Vertex( key );
00844                         m_vertices.insert( key, exp );
00845                     }
00846                     // Then create the appropriate edges
00847                     importIt = ( *it )->import.begin();
00848                     for ( ; importIt != importEnd; ++importIt )
00849                         m_vertices[ ( *importIt ).latin1() ]->addEdge( new Edge( exp, *it ) );
00850                 }
00851             }
00852             else
00853                 kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " doesn't apply." << endl;
00854         }
00855     }
00856 
00857     // As all edges (=filters) are required to have a positive weight
00858     // we can use Dijkstra's shortest path algorithm from Cormen's
00859     // "Introduction to Algorithms" (p. 527)
00860     // Note: I did some adaptions as our data structures are slightly
00861     // different from the ones used in the book. Further we simply stop
00862     // the algorithm is we don't find any node with a weight != Infinity
00863     // (==UINT_MAX), as this means that the remaining nodes in the queue
00864     // aren't connected anyway.
00865     void Graph::shortestPaths()
00866     {
00867         // Is the requested start mime type valid?
00868         Vertex* from = m_vertices[ m_from ];
00869         if ( !from )
00870             return;
00871 
00872         // Inititalize start vertex
00873         from->setKey( 0 );
00874 
00875         // Fill the priority queue with all the vertices
00876         PriorityQueue<Vertex> queue( m_vertices );
00877 
00878         while ( !queue.isEmpty() ) {
00879             Vertex *min = queue.extractMinimum();
00880             // Did we already relax all connected vertices?
00881             if ( min->key() == UINT_MAX )
00882                 break;
00883             min->relaxVertices( queue );
00884         }
00885         m_graphValid = true;
00886     }
00887 
00888     QCString Graph::findKOfficePart() const
00889     {
00890         // Here we simply try to find the closest KOffice mimetype
00891         QValueList<KoDocumentEntry> parts( KoDocumentEntry::query() );
00892         QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
00893         QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );
00894 
00895         const Vertex *v = 0;
00896 
00897         // Be sure that v gets initialized correctly
00898         while ( !v && partIt != partEnd ) {
00899             QString key( ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString() );
00900             if ( !key.isEmpty() )
00901                 v = m_vertices[ key.latin1() ];
00902             ++partIt;
00903         }
00904         if ( !v )
00905             return "";
00906 
00907         // Now we try to find the "cheapest" KOffice vertex
00908         while ( partIt != partEnd ) {
00909             QString key( ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString() );
00910             Vertex* tmp = 0;
00911             if ( !key.isEmpty() )
00912                 tmp = m_vertices[ key.latin1() ];
00913 
00914             if ( tmp && tmp->key() < v->key() )
00915                 v = tmp;
00916             ++partIt;
00917         }
00918 
00919         // It seems it already is a KOffice part
00920         if ( v->key() == 0 )
00921             return "";
00922 
00923         return v->mimeType();
00924     }
00925 
00926 } // namespace KOffice
KDE Logo
This file is part of the documentation for lib Library Version 1.3.5.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sun Mar 20 14:25:25 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003