lib

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., 51 Franklin Street, Fifth Floor,
00016  * Boston, MA 02110-1301, 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     if (status == KoFilter::OK)
00171       finalizeIO();
00172     return status;
00173 }
00174 
00175 QString KoFilterChain::chainOutput() const
00176 {
00177     if ( m_state == Done )
00178         return m_inputFile; // as we already called manageIO()
00179     return QString::null;
00180 }
00181 
00182 QString KoFilterChain::inputFile()
00183 {
00184     if ( m_inputQueried == File )
00185         return m_inputFile;
00186     else if ( m_inputQueried != Nil ) {
00187         kdWarning( 30500 ) << "You already asked for some different source." << endl;
00188         return QString::null;
00189     }
00190     m_inputQueried = File;
00191 
00192     if ( m_state & Beginning ) {
00193         if ( static_cast<KoFilterManager::Direction>( filterManagerDirection() ) ==
00194              KoFilterManager::Import )
00195             m_inputFile = filterManagerImportFile();
00196         else
00197             inputFileHelper( filterManagerKoDocument(), filterManagerImportFile() );
00198     }
00199     else
00200         if ( m_inputFile.isEmpty() )
00201             inputFileHelper( m_inputDocument, QString::null );
00202 
00203     return m_inputFile;
00204 }
00205 
00206 QString KoFilterChain::outputFile()
00207 {
00208     // sanity check: No embedded filter should ask for a plain file
00209     // ###### CHECK: This will break as soon as we support exporting embedding filters
00210     if ( filterManagerParentChain() )
00211         kdWarning( 30500 )<< "An embedded filter has to use storageFile()!" << endl;
00212 
00213     if ( m_outputQueried == File )
00214         return m_outputFile;
00215     else if ( m_outputQueried != Nil ) {
00216         kdWarning( 30500 ) << "You already asked for some different destination." << endl;
00217         return QString::null;
00218     }
00219     m_outputQueried = File;
00220 
00221     if ( m_state & End ) {
00222         if ( static_cast<KoFilterManager::Direction>( filterManagerDirection() ) ==
00223              KoFilterManager::Import )
00224             outputFileHelper( false );  // This (last) one gets deleted by the caller
00225         else
00226             m_outputFile = filterManagerExportFile();
00227     }
00228     else
00229         outputFileHelper( true );
00230 
00231     return m_outputFile;
00232 }
00233 
00234 KoStoreDevice* KoFilterChain::storageFile( const QString& name, KoStore::Mode mode )
00235 {
00236     // ###### CHECK: This works only for import filters. Do we want something like
00237     // that for export filters too?
00238     if ( m_outputQueried == Nil && mode == KoStore::Write && filterManagerParentChain() )
00239         return storageInitEmbedding( name );
00240 
00241     // Plain normal use case
00242     if ( m_inputQueried == Storage && mode == KoStore::Read &&
00243          m_inputStorage && m_inputStorage->mode() == KoStore::Read )
00244         return storageNewStreamHelper( &m_inputStorage, &m_inputStorageDevice, name );
00245     else if ( m_outputQueried == Storage && mode == KoStore::Write &&
00246               m_outputStorage && m_outputStorage->mode() == KoStore::Write )
00247         return storageNewStreamHelper( &m_outputStorage, &m_outputStorageDevice, name );
00248     else if ( m_inputQueried == Nil && mode == KoStore::Read )
00249         return storageHelper( inputFile(), name, KoStore::Read,
00250                               &m_inputStorage, &m_inputStorageDevice );
00251     else if ( m_outputQueried == Nil && mode == KoStore::Write )
00252         return storageHelper( outputFile(), name, KoStore::Write,
00253                               &m_outputStorage, &m_outputStorageDevice );
00254     else {
00255         kdWarning( 30500 ) << "Oooops, how did we get here? You already asked for a"
00256                            << " different source/destination?" << endl;
00257         return 0;
00258     }
00259 }
00260 
00261 KoDocument* KoFilterChain::inputDocument()
00262 {
00263     if ( m_inputQueried == Document )
00264         return m_inputDocument;
00265     else if ( m_inputQueried != Nil ) {
00266         kdWarning( 30500 ) << "You already asked for some different source." << endl;
00267         return 0;
00268     }
00269 
00270     if ( ( m_state & Beginning ) &&
00271          static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == KoFilterManager::Export &&
00272          filterManagerKoDocument() )
00273         m_inputDocument = filterManagerKoDocument();
00274     else if ( !m_inputDocument )
00275         m_inputDocument = createDocument( inputFile() );
00276 
00277     m_inputQueried = Document;
00278     return m_inputDocument;
00279 }
00280 
00281 KoDocument* KoFilterChain::outputDocument()
00282 {
00283     // sanity check: No embedded filter should ask for a document
00284     // ###### CHECK: This will break as soon as we support exporting embedding filters
00285     if ( filterManagerParentChain() ) {
00286         kdWarning( 30500 )<< "An embedded filter has to use storageFile()!" << endl;
00287         return 0;
00288     }
00289 
00290     if ( m_outputQueried == Document )
00291         return m_outputDocument;
00292     else if ( m_outputQueried != Nil ) {
00293         kdWarning( 30500 ) << "You already asked for some different destination." << endl;
00294         return 0;
00295     }
00296 
00297     if ( ( m_state & End ) &&
00298          static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == KoFilterManager::Import &&
00299          filterManagerKoDocument() )
00300         m_outputDocument = filterManagerKoDocument();
00301     else
00302         m_outputDocument = createDocument( m_chainLinks.current()->to() );
00303 
00304     m_outputQueried = Document;
00305     return m_outputDocument;
00306 }
00307 
00308 void KoFilterChain::dump() const
00309 {
00310     kdDebug( 30500 ) << "########## KoFilterChain with " << m_chainLinks.count() << " members:" << endl;
00311     QPtrListIterator<ChainLink> it( m_chainLinks );
00312     for ( ; it.current(); ++it )
00313         it.current()->dump();
00314     kdDebug( 30500 ) << "########## KoFilterChain (done) ##########" << endl;
00315 }
00316 
00317 KoFilterChain::KoFilterChain( const KoFilterManager* manager ) :
00318     m_manager( manager ), m_state( Beginning ), m_inputStorage( 0 ),
00319     m_inputStorageDevice( 0 ), m_outputStorage( 0 ), m_outputStorageDevice( 0 ),
00320     m_inputDocument( 0 ), m_outputDocument( 0 ), m_inputTempFile( 0 ),
00321     m_outputTempFile( 0 ), m_inputQueried( Nil ), m_outputQueried( Nil ), d( 0 )
00322 {
00323     // We "own" our chain links, the filter entries are implicitly shared
00324     m_chainLinks.setAutoDelete( true );
00325 }
00326 
00327 void KoFilterChain::appendChainLink( KoFilterEntry::Ptr filterEntry, const QCString& from, const QCString& to )
00328 {
00329     m_chainLinks.append( new ChainLink( this, filterEntry, from, to ) );
00330 }
00331 
00332 void KoFilterChain::prependChainLink( KoFilterEntry::Ptr filterEntry, const QCString& from, const QCString& to )
00333 {
00334     m_chainLinks.prepend( new ChainLink( this, filterEntry, from, to ) );
00335 }
00336 
00337 void KoFilterChain::enterDirectory( const QString& directory )
00338 {
00339     // Only a little bit of checking as we (have to :} ) trust KoEmbeddingFilter
00340     // If the output storage isn't initialized yet, we perform that step(s) on init.
00341     if ( m_outputStorage )
00342         m_outputStorage->enterDirectory( directory );
00343     m_internalEmbeddingDirectories.append( directory );
00344 }
00345 
00346 void KoFilterChain::leaveDirectory()
00347 {
00348     if ( m_outputStorage )
00349         m_outputStorage->leaveDirectory();
00350     if ( !m_internalEmbeddingDirectories.isEmpty() )
00351         m_internalEmbeddingDirectories.pop_back();
00352 }
00353 
00354 QString KoFilterChain::filterManagerImportFile() const
00355 {
00356     return m_manager->importFile();
00357 }
00358 
00359 QString KoFilterChain::filterManagerExportFile() const
00360 {
00361     return m_manager->exportFile();
00362 }
00363 
00364 KoDocument* KoFilterChain::filterManagerKoDocument() const
00365 {
00366     return m_manager->document();
00367 }
00368 
00369 int KoFilterChain::filterManagerDirection() const
00370 {
00371     return m_manager->direction();
00372 }
00373 
00374 KoFilterChain* const KoFilterChain::filterManagerParentChain() const
00375 {
00376     return m_manager->parentChain();
00377 }
00378 
00379 void KoFilterChain::manageIO()
00380 {
00381     m_inputQueried = Nil;
00382     m_outputQueried = Nil;
00383 
00384     delete m_inputStorageDevice;
00385     m_inputStorageDevice = 0;
00386     if ( m_inputStorage ) {
00387         m_inputStorage->close();
00388         delete m_inputStorage;
00389         m_inputStorage = 0;
00390     }
00391     if ( m_inputTempFile ) {
00392         m_inputTempFile->close();
00393         delete m_inputTempFile;  // autodelete
00394         m_inputTempFile = 0;
00395     }
00396     m_inputFile = QString::null;
00397 
00398     if ( !m_outputFile.isEmpty() ) {
00399         m_inputFile = m_outputFile;
00400         m_outputFile = QString::null;
00401         m_inputTempFile = m_outputTempFile;
00402         m_outputTempFile = 0;
00403 
00404         delete m_outputStorageDevice;
00405         m_outputStorageDevice = 0;
00406         if ( m_outputStorage ) {
00407             m_outputStorage->close();
00408             // Don't delete the storage if we're just pointing to the
00409             // storage of the parent filter chain
00410             if ( !filterManagerParentChain() || m_outputStorage->mode() != KoStore::Write )
00411                 delete m_outputStorage;
00412             m_outputStorage = 0;
00413         }
00414     }
00415 
00416     if ( m_inputDocument != filterManagerKoDocument() )
00417         delete m_inputDocument;
00418     m_inputDocument = m_outputDocument;
00419     m_outputDocument = 0;
00420 }
00421 
00422 void KoFilterChain::finalizeIO()
00423 {
00424     // In case we export (to a file, of course) and the last
00425     // filter chose to output a KoDocument we have to save it.
00426     // Should be very rare, but well...
00427     // Note: m_*input*Document as we already called manageIO()
00428     if ( m_inputDocument &&
00429          static_cast<KoFilterManager::Direction>( filterManagerDirection() ) == KoFilterManager::Export ) {
00430         kdDebug( 30500 ) << "Saving the output document to the export file" << endl;
00431         m_inputDocument->saveNativeFormat( filterManagerExportFile() );
00432         m_inputFile = filterManagerExportFile();
00433     }
00434 }
00435 
00436 bool KoFilterChain::createTempFile( KTempFile** tempFile, bool autoDelete )
00437 {
00438     if ( *tempFile ) {
00439         kdError( 30500 ) << "Ooops, why is there already a temp file???" << endl;
00440         return false;
00441     }
00442     *tempFile = new KTempFile();
00443     ( *tempFile )->setAutoDelete( autoDelete );
00444     return ( *tempFile )->status() == 0;
00445 }
00446 
00447 void KoFilterChain::inputFileHelper( KoDocument* document, const QString& alternativeFile )
00448 {
00449     if ( document ) {
00450         if ( !createTempFile( &m_inputTempFile ) ) {
00451             delete m_inputTempFile;
00452             m_inputTempFile = 0;
00453             m_inputFile = QString::null;
00454             return;
00455         }
00456         if ( !document->saveNativeFormat( m_inputTempFile->name() ) ) {
00457             delete m_inputTempFile;
00458             m_inputTempFile = 0;
00459             m_inputFile = QString::null;
00460             return;
00461         }
00462         m_inputFile = m_inputTempFile->name();
00463     }
00464     else
00465         m_inputFile = alternativeFile;
00466 }
00467 
00468 void KoFilterChain::outputFileHelper( bool autoDelete )
00469 {
00470     if ( !createTempFile( &m_outputTempFile, autoDelete ) ) {
00471         delete m_outputTempFile;
00472         m_outputTempFile = 0;
00473         m_outputFile = QString::null;
00474     }
00475     else
00476         m_outputFile = m_outputTempFile->name();
00477 }
00478 
00479 KoStoreDevice* KoFilterChain::storageNewStreamHelper( KoStore** storage, KoStoreDevice** device,
00480                                                       const QString& name )
00481 {
00482     delete *device;
00483     *device = 0;
00484     if ( ( *storage )->isOpen() )
00485         ( *storage )->close();
00486     if ( ( *storage )->bad() )
00487         return storageCleanupHelper( storage );
00488     if ( !( *storage )->open( name ) )
00489         return 0;
00490 
00491     *device = new KoStoreDevice( *storage );
00492     return *device;
00493 }
00494 
00495 KoStoreDevice* KoFilterChain::storageHelper( const QString& file, const QString& streamName,
00496                                              KoStore::Mode mode, KoStore** storage,
00497                                              KoStoreDevice** device )
00498 {
00499     if ( file.isEmpty() )
00500         return 0;
00501     if ( *storage ) {
00502         kdDebug( 30500 ) << "Uh-oh, we forgot to clean up..." << endl;
00503         return 0;
00504     }
00505 
00506     storageInit( file, mode, storage );
00507 
00508     if ( ( *storage )->bad() )
00509         return storageCleanupHelper( storage );
00510 
00511     // Seems that we got a valid storage, at least. Even if we can't open
00512     // the stream the "user" asked us to open, we nontheless change the
00513     // IOState from File to Storage, as it might be possible to open other streams
00514     if ( mode == KoStore::Read )
00515         m_inputQueried = Storage;
00516     else // KoStore::Write
00517         m_outputQueried = Storage;
00518 
00519     return storageCreateFirstStream( streamName, storage, device );
00520 }
00521 
00522 void KoFilterChain::storageInit( const QString& file, KoStore::Mode mode, KoStore** storage )
00523 {
00524     QCString appIdentification( "" );
00525     if ( mode == KoStore::Write ) {
00526         // To create valid storages we also have to add the mimetype
00527         // magic "applicationIndentifier" to the storage.
00528         // As only filters with a KOffice destination should query
00529         // for a storage to write to, we don't check the content of
00530         // the mimetype here. It doesn't do a lot of harm if someome
00531         // "abuses" this method.
00532         appIdentification = m_chainLinks.current()->to();
00533     }
00534     *storage = KoStore::createStore( file, mode, appIdentification );
00535 }
00536 
00537 KoStoreDevice* KoFilterChain::storageInitEmbedding( const QString& name )
00538 {
00539     if ( m_outputStorage ) {
00540         kdWarning( 30500 ) << "Ooops! Something's really screwed here." << endl;
00541         return 0;
00542     }
00543 
00544     m_outputStorage = filterManagerParentChain()->m_outputStorage;
00545 
00546     if ( !m_outputStorage ) {
00547         // If the storage of the parent hasn't been initialized yet,
00548         // we have to do that here. Quite nasty...
00549         storageInit( filterManagerParentChain()->outputFile(), KoStore::Write, &m_outputStorage );
00550 
00551         // transfer the ownership
00552         filterManagerParentChain()->m_outputStorage = m_outputStorage;
00553         filterManagerParentChain()->m_outputQueried = Storage;
00554     }
00555 
00556     if ( m_outputStorage->isOpen() )
00557         m_outputStorage->close();  // to be on the safe side, should never happen
00558     if ( m_outputStorage->bad() )
00559         return storageCleanupHelper( &m_outputStorage );
00560 
00561     m_outputQueried = Storage;
00562 
00563     // Now that we have a storage we have to change the directory
00564     // and remember it for later!
00565     const int lruPartIndex = filterManagerParentChain()->m_chainLinks.current()->lruPartIndex();
00566     if ( lruPartIndex == -1 ) {
00567         kdError( 30500 ) << "Huh! You want to use embedding features w/o inheriting KoEmbeddingFilter?" << endl;
00568         return storageCleanupHelper( &m_outputStorage );
00569     }
00570 
00571     if ( !m_outputStorage->enterDirectory( QString( "part%1" ).arg( lruPartIndex ) ) )
00572         return storageCleanupHelper( &m_outputStorage );
00573 
00574     return storageCreateFirstStream( name, &m_outputStorage, &m_outputStorageDevice );
00575 }
00576 
00577 KoStoreDevice* KoFilterChain::storageCreateFirstStream( const QString& streamName, KoStore** storage,
00578                                                         KoStoreDevice** device )
00579 {
00580     // Before we go and create the first stream in this storage we
00581     // have to perform a little hack in case we're used by any ole-style
00582     // filter which utilizes internal embedding. Ugly, but well...
00583     if ( !m_internalEmbeddingDirectories.isEmpty() ) {
00584         QStringList::ConstIterator it = m_internalEmbeddingDirectories.begin();
00585         QStringList::ConstIterator end = m_internalEmbeddingDirectories.end();
00586         for ( ; it != end && ( *storage )->enterDirectory( *it ); ++it );
00587     }
00588 
00589     if ( !( *storage )->open( streamName ) )
00590         return 0;
00591 
00592     if ( *device ) {
00593         kdDebug( 30500 ) << "Uh-oh, we forgot to clean up the storage device!" << endl;
00594         ( *storage )->close();
00595         return storageCleanupHelper( storage );
00596     }
00597     *device = new KoStoreDevice( *storage );
00598     return *device;
00599 }
00600 
00601 KoStoreDevice* KoFilterChain::storageCleanupHelper( KoStore** storage )
00602 {
00603     // Take care not to delete the storage of the parent chain
00604     if ( *storage != m_outputStorage || !filterManagerParentChain() ||
00605          ( *storage )->mode() != KoStore::Write )
00606         delete *storage;
00607     *storage = 0;
00608     return 0;
00609 }
00610 
00611 KoDocument* KoFilterChain::createDocument( const QString& file )
00612 {
00613     KURL url;
00614     url.setPath( file );
00615     KMimeType::Ptr t = KMimeType::findByURL( url, 0, true );
00616     if ( t->name() == KMimeType::defaultMimeType() ) {
00617         kdError( 30500 ) << "No mimetype found for " << file << endl;
00618         return 0;
00619     }
00620 
00621     KoDocument *doc = createDocument( QCString( t->name().latin1() ) );
00622 
00623     if ( !doc || !doc->loadNativeFormat( file ) ) {
00624         kdError( 30500 ) << "Couldn't load from the file" << endl;
00625         delete doc;
00626         return 0;
00627     }
00628     return doc;
00629 }
00630 
00631 KoDocument* KoFilterChain::createDocument( const QCString& mimeType )
00632 {
00633     KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(mimeType);
00634 
00635     if (entry.isEmpty())
00636     {
00637         kdError( 30500 ) << "Couldn't find a part that can handle mimetype " << mimeType << endl;
00638     }
00639     
00640     KoDocument* doc = entry.createDoc(); /*entries.first().createDoc();*/
00641     if ( !doc ) {
00642         kdError( 30500 ) << "Couldn't create the document" << endl;
00643         return 0;
00644     }
00645     return doc;
00646 }
00647 
00648 
00649 namespace KOffice {
00650 
00651     Edge::Edge( Vertex* vertex, KoFilterEntry::Ptr filterEntry ) :
00652         m_vertex( vertex ), m_filterEntry( filterEntry ), d( 0 )
00653     {
00654     }
00655 
00656     void Edge::relax( const Vertex* predecessor, PriorityQueue<Vertex>& queue )
00657     {
00658         if ( !m_vertex || !predecessor || !m_filterEntry )
00659             return;
00660         if ( m_vertex->setKey( predecessor->key() + m_filterEntry->weight ) ) {
00661             queue.keyDecreased( m_vertex ); // maintain the heap property
00662             m_vertex->setPredecessor( predecessor );
00663         }
00664     }
00665 
00666     void Edge::dump( const QCString& indent ) const
00667     {
00668         if ( m_vertex )
00669             kdDebug( 30500 ) << indent << "Edge -> '" << m_vertex->mimeType()
00670                              << "' (" << m_filterEntry->weight << ")" << endl;
00671         else
00672             kdDebug( 30500 ) << indent << "Edge -> '(null)' ("
00673                              << m_filterEntry->weight << ")" << endl;
00674     }
00675 
00676 
00677     Vertex::Vertex( const QCString& mimeType ) : m_predecessor( 0 ), m_mimeType( mimeType ),
00678         m_weight( UINT_MAX ), m_index( -1 ), d( 0 )
00679     {
00680         m_edges.setAutoDelete( true );  // we take ownership of added edges
00681     }
00682 
00683     bool Vertex::setKey( unsigned int key )
00684     {
00685         if ( m_weight > key ) {
00686             m_weight = key;
00687             return true;
00688         }
00689         return false;
00690     }
00691 
00692     void Vertex::reset()
00693     {
00694         m_weight = UINT_MAX;
00695         m_predecessor = 0;
00696     }
00697 
00698     void Vertex::addEdge( const Edge* edge )
00699     {
00700         if ( !edge || edge->weight() == 0 )
00701             return;
00702         m_edges.append( edge );
00703     }
00704 
00705     const Edge* Vertex::findEdge( const Vertex* vertex ) const
00706     {
00707         if ( !vertex )
00708             return 0;
00709         const Edge* edge = 0;
00710         QPtrListIterator<Edge> it( m_edges );
00711 
00712         for ( ; it.current(); ++it ) {
00713             if ( it.current()->vertex() == vertex &&
00714                  ( !edge || it.current()->weight() < edge->weight() ) )
00715                 edge = it.current();
00716         }
00717         return edge;
00718     }
00719 
00720     void Vertex::relaxVertices( PriorityQueue<Vertex>& queue )
00721     {
00722         for ( Edge *e = m_edges.first(); e; e = m_edges.next() )
00723             e->relax( this, queue );
00724     }
00725 
00726     void Vertex::dump( const QCString& indent ) const
00727     {
00728         kdDebug( 30500 ) << indent << "Vertex: " << m_mimeType << " (" << m_weight << "):" << endl;
00729         const QCString i( indent + "   " );
00730         QPtrListIterator<Edge> it( m_edges );
00731         for ( ; it.current(); ++it )
00732             it.current()->dump( i );
00733     }
00734 
00735 
00736     Graph::Graph( const QCString& from ) : m_vertices( 47 ), m_from( from ),
00737                                            m_graphValid( false ), d( 0 )
00738     {
00739         m_vertices.setAutoDelete( true );
00740         buildGraph();
00741         shortestPaths();  // Will return after a single lookup if "from" is invalid (->no check here)
00742     }
00743 
00744     void Graph::setSourceMimeType( const QCString& from )
00745     {
00746         if ( from == m_from )
00747             return;
00748         m_from = from;
00749         m_graphValid = false;
00750 
00751         // Initialize with "infinity" ...
00752         QAsciiDictIterator<Vertex> it( m_vertices );
00753         for ( ; it.current(); ++it )
00754             it.current()->reset();
00755 
00756         // ...and re-run the shortest path search for the new source mime
00757         shortestPaths();
00758     }
00759 
00760     KoFilterChain::Ptr Graph::chain( const KoFilterManager* manager, QCString& to ) const
00761     {
00762         if ( !isValid() || !manager )
00763             return 0;
00764 
00765         if ( to.isEmpty() ) {  // if the destination is empty we search the closest KOffice part
00766             to = findKOfficePart();
00767             if ( to.isEmpty() )  // still empty? strange stuff...
00768                 return 0;
00769         }
00770 
00771         const Vertex* vertex = m_vertices[ to ];
00772         if ( !vertex || vertex->key() == UINT_MAX )
00773             return 0;
00774 
00775         KoFilterChain::Ptr ret = new KoFilterChain( manager );
00776 
00777         // Fill the filter chain with all filters on the path
00778         const Vertex* tmp = vertex->predecessor();
00779         while ( tmp ) {
00780             const Edge* const edge = tmp->findEdge( vertex );
00781             Q_ASSERT( edge );
00782             ret->prependChainLink( edge->filterEntry(), tmp->mimeType(), vertex->mimeType() );
00783             vertex = tmp;
00784             tmp = tmp->predecessor();
00785         }
00786         return ret;
00787     }
00788 
00789     void Graph::dump() const
00790     {
00791         kdDebug( 30500 ) << "+++++++++ Graph::dump +++++++++" << endl;
00792         kdDebug( 30500 ) << "From: " << m_from << endl;
00793         QAsciiDictIterator<Vertex> it( m_vertices );
00794         for ( ; it.current(); ++it )
00795             it.current()->dump( "   " );
00796         kdDebug( 30500 ) << "+++++++++ Graph::dump (done) +++++++++" << endl;
00797     }
00798 
00799     // Query the trader and create the vertices and edges representing
00800     // available mime types and filters.
00801     void Graph::buildGraph()
00802     {
00803         // Make sure that all available parts are added to the graph
00804         QValueList<KoDocumentEntry> parts( KoDocumentEntry::query() );
00805         QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
00806         QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );
00807 
00808         while ( partIt != partEnd ) {
00809             QStringList nativeMimeTypes = ( *partIt ).service()->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList();
00810             nativeMimeTypes += ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString();
00811             QStringList::ConstIterator it = nativeMimeTypes.begin();
00812             QStringList::ConstIterator end = nativeMimeTypes.end();
00813             for ( ; it != end; ++it )
00814                 if ( !(*it).isEmpty() )
00815                     m_vertices.insert( (*it).latin1(), new Vertex( (*it).latin1() ) );
00816             ++partIt;
00817         }
00818 
00819         QValueList<KoFilterEntry::Ptr> filters( KoFilterEntry::query() ); // no constraint here - we want *all* :)
00820         QValueList<KoFilterEntry::Ptr>::ConstIterator it = filters.begin();
00821         QValueList<KoFilterEntry::Ptr>::ConstIterator end = filters.end();
00822 
00823         for ( ; it != end; ++it ) {
00824             // First add the "starting points" to the dict
00825             QStringList::ConstIterator importIt = ( *it )->import.begin();
00826             QStringList::ConstIterator importEnd = ( *it )->import.end();
00827             for ( ; importIt != importEnd; ++importIt ) {
00828                 const QCString key = ( *importIt ).latin1();  // latin1 is okay here (werner)
00829                 // already there?
00830                 if ( !m_vertices[ key ] )
00831                     m_vertices.insert( key, new Vertex( key ) );
00832             }
00833 
00834             // Are we allowed to use this filter at all?
00835             if ( KoFilterManager::filterAvailable( *it ) ) {
00836                 QStringList::ConstIterator exportIt = ( *it )->export_.begin();
00837                 QStringList::ConstIterator exportEnd = ( *it )->export_.end();
00838 
00839                 for ( ; exportIt != exportEnd; ++exportIt ) {
00840                     // First make sure the export vertex is in place
00841                     const QCString key = ( *exportIt ).latin1();  // latin1 is okay here
00842                     Vertex* exp = m_vertices[ key ];
00843                     if ( !exp ) {
00844                         exp = new Vertex( key );
00845                         m_vertices.insert( key, exp );
00846                     }
00847                     // Then create the appropriate edges
00848                     importIt = ( *it )->import.begin();
00849                     for ( ; importIt != importEnd; ++importIt )
00850                         m_vertices[ ( *importIt ).latin1() ]->addEdge( new Edge( exp, *it ) );
00851                 }
00852             }
00853             else
00854                 kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " doesn't apply." << endl;
00855         }
00856     }
00857 
00858     // As all edges (=filters) are required to have a positive weight
00859     // we can use Dijkstra's shortest path algorithm from Cormen's
00860     // "Introduction to Algorithms" (p. 527)
00861     // Note: I did some adaptions as our data structures are slightly
00862     // different from the ones used in the book. Further we simply stop
00863     // the algorithm is we don't find any node with a weight != Infinity
00864     // (==UINT_MAX), as this means that the remaining nodes in the queue
00865     // aren't connected anyway.
00866     void Graph::shortestPaths()
00867     {
00868         // Is the requested start mime type valid?
00869         Vertex* from = m_vertices[ m_from ];
00870         if ( !from )
00871             return;
00872 
00873         // Inititalize start vertex
00874         from->setKey( 0 );
00875 
00876         // Fill the priority queue with all the vertices
00877         PriorityQueue<Vertex> queue( m_vertices );
00878 
00879         while ( !queue.isEmpty() ) {
00880             Vertex *min = queue.extractMinimum();
00881             // Did we already relax all connected vertices?
00882             if ( min->key() == UINT_MAX )
00883                 break;
00884             min->relaxVertices( queue );
00885         }
00886         m_graphValid = true;
00887     }
00888 
00889     QCString Graph::findKOfficePart() const
00890     {
00891         // Here we simply try to find the closest KOffice mimetype
00892         QValueList<KoDocumentEntry> parts( KoDocumentEntry::query() );
00893         QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
00894         QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );
00895 
00896         const Vertex *v = 0;
00897 
00898         // Be sure that v gets initialized correctly
00899         while ( !v && partIt != partEnd ) {
00900             QStringList nativeMimeTypes = ( *partIt ).service()->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList();
00901             nativeMimeTypes += ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString();
00902             QStringList::ConstIterator it = nativeMimeTypes.begin();
00903             QStringList::ConstIterator end = nativeMimeTypes.end();
00904             for ( ; !v && it != end; ++it )
00905                 if ( !(*it).isEmpty() )
00906                     v = m_vertices[ ( *it ).latin1() ];
00907             ++partIt;
00908         }
00909         if ( !v )
00910             return "";
00911 
00912         // Now we try to find the "cheapest" KOffice vertex
00913         while ( partIt != partEnd ) {
00914             QStringList nativeMimeTypes = ( *partIt ).service()->property( "X-KDE-ExtraNativeMimeTypes" ).toStringList();
00915             nativeMimeTypes += ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString();
00916             QStringList::ConstIterator it = nativeMimeTypes.begin();
00917             QStringList::ConstIterator end = nativeMimeTypes.end();
00918             for ( ; !v && it != end; ++it ) {
00919                 QString key = *it;
00920                 if ( !key.isEmpty() ) {
00921                     Vertex* tmp = m_vertices[ key.latin1() ];
00922                     if ( !v || ( tmp && tmp->key() < v->key() ) )
00923                         v = tmp;
00924                 }
00925             }
00926             ++partIt;
00927         }
00928 
00929         // It seems it already is a KOffice part
00930         if ( v->key() == 0 )
00931             return "";
00932 
00933         return v->mimeType();
00934     }
00935 
00936 } // namespace KOffice
KDE Home | KDE Accessibility Home | Description of Access Keys