lib Library API Documentation

koFilterManager.cc

00001 /* This file is part of the KDE project
00002    Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
00003                  2000, 2001 Werner Trobin <trobin@kde.org>
00004    Copyright (C) 2004 Nicolas Goutte <goutte@kde.org>
00005 
00006    This library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License as published by the Free Software Foundation; either
00009    version 2 of the License, or (at your option) any later version.
00010 
00011    This library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public License
00017    along with this library; see the file COPYING.LIB.  If not, write to
00018    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00019    Boston, MA 02111-1307, USA.
00020 */
00021 
00022 
00023 #include <koFilterManager.h>
00024 #include <koFilterManager_p.h>
00025 
00026 #include <qfile.h>
00027 #include <qlabel.h>
00028 #include <qlayout.h>
00029 #include <qptrlist.h>
00030 #include <qapplication.h>
00031 
00032 #include <klocale.h>
00033 #include <kmessagebox.h>
00034 #include <koDocument.h>
00035 #include <klibloader.h>
00036 #include <klistbox.h>
00037 #include <kmimetype.h>
00038 #include <kdebug.h>
00039 
00040 #include <queue>
00041 
00042 #include <unistd.h>
00043 
00044 class KoFilterManager::Private
00045 {
00046 public:
00047     bool m_batch;
00048 };
00049 
00050 
00051 KoFilterChooser::KoFilterChooser (QWidget *parent, const QStringList &mimeTypes, const QString &nativeFormat)
00052     : KDialogBase (parent, "kofilterchooser", true, i18n ("Choose Filter"),
00053                    KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true),
00054     m_mimeTypes (mimeTypes)
00055 {
00056     setInitialSize (QSize (300, 350));
00057 
00058     QWidget *page = new QWidget (this);
00059     setMainWidget (page);
00060 
00061     // looks too squashed together without * 2
00062     QVBoxLayout *layout = new QVBoxLayout (page, marginHint (), spacingHint () * 2);
00063 
00064     QLabel *filterLabel = new QLabel (i18n ("Select a filter:"), page, "filterlabel");
00065     layout->addWidget (filterLabel);
00066 
00067     m_filterList = new KListBox (page, "filterlist");
00068     layout->addWidget (m_filterList);
00069 
00070     Q_ASSERT (!m_mimeTypes.isEmpty ());
00071     for (QStringList::ConstIterator it = m_mimeTypes.begin ();
00072             it != m_mimeTypes.end ();
00073             it++)
00074     {
00075         KMimeType::Ptr mime = KMimeType::mimeType (*it);
00076         m_filterList->insertItem (mime->comment ());
00077     }
00078 
00079     if (nativeFormat == "application/x-kword")
00080     {
00081         const int index = m_mimeTypes.findIndex ("text/plain");
00082         if (index > -1)
00083             m_filterList->setCurrentItem (index);
00084     }
00085 
00086     if (m_filterList->currentItem () == -1)
00087         m_filterList->setCurrentItem (0);
00088 
00089     m_filterList->centerCurrentItem ();
00090     m_filterList->setFocus ();
00091 
00092     connect (m_filterList, SIGNAL (selected (int)), this, SLOT (slotOk ()));
00093 }
00094 
00095 KoFilterChooser::~KoFilterChooser ()
00096 {
00097 }
00098 
00099 QString KoFilterChooser::filterSelected ()
00100 {
00101     const int item = m_filterList->currentItem ();
00102 
00103     if (item > -1)
00104         return m_mimeTypes [item];
00105     else
00106         return QString::null;
00107 }
00108 
00109 
00110 // static cache for filter availability
00111 QMap<QString, bool> KoFilterManager::m_filterAvailable;
00112 
00113 const int KoFilterManager::s_area = 30500;
00114 
00115 
00116 KoFilterManager::KoFilterManager( KoDocument* document ) :
00117     m_document( document ), m_parentChain( 0 ), m_graph( "" ), d( 0 )
00118 {
00119     d = new KoFilterManager::Private;
00120     d -> m_batch = false;
00121     if ( document )
00122         QObject::connect( this, SIGNAL( sigProgress( int ) ),
00123                           document, SIGNAL( sigProgress( int ) ) );
00124 }
00125 
00126 
00127 KoFilterManager::KoFilterManager( const QString& url, const QCString& mimetypeHint,
00128                                   KoFilterChain* const parentChain ) :
00129     m_document( 0 ), m_parentChain( parentChain ), m_importUrl( url ), m_importUrlMimetypeHint( mimetypeHint ),
00130     m_graph( "" ), d( 0 )
00131 {
00132     d = new KoFilterManager::Private;
00133     d -> m_batch = false;
00134 }
00135 
00136 KoFilterManager::~KoFilterManager()
00137 {
00138     delete d;
00139 }
00140 
00141 QString KoFilterManager::import( const QString& url, KoFilter::ConversionStatus& status )
00142 {
00143     // Find the mime type for the file to be imported.
00144     KURL u;
00145     u.setPath( url );
00146     KMimeType::Ptr t = KMimeType::findByURL( u, 0, true );
00147     if ( t->name() == KMimeType::defaultMimeType() ) {
00148         kdError(s_area) << "No mimetype found for " << url << endl;
00149         status = KoFilter::BadMimeType;
00150         return QString::null;
00151     }
00152 
00153     m_graph.setSourceMimeType( t->name().latin1() );  // .latin1() is okay here (Werner)
00154     if ( !m_graph.isValid() ) {
00155         bool userCancelled = false;
00156 
00157         kdWarning(s_area) << "Can't open " << t->name () << ", trying filter chooser" << endl;
00158         if ( m_document )
00159         {
00160         if ( !m_document->isAutoErrorHandlingEnabled() )
00161         {
00162         status = KoFilter::BadConversionGraph;
00163         return QString::null;
00164         }
00165             QCString nativeFormat = m_document->nativeFormatMimeType ();
00166 
00167             QApplication::setOverrideCursor( arrowCursor );
00168             KoFilterChooser chooser(0,
00169                                     KoFilterManager::mimeFilter (nativeFormat, KoFilterManager::Import, m_document->extraNativeMimeTypes()),
00170                                     nativeFormat);
00171             if (chooser.exec ())
00172             {
00173                 QCString f = chooser.filterSelected ().latin1();
00174 
00175                 if (f == nativeFormat)
00176                 {
00177                     status = KoFilter::OK;
00178                     QApplication::restoreOverrideCursor();
00179                     return url;
00180                 }
00181 
00182                 m_graph.setSourceMimeType (f);
00183             }
00184             else
00185                 userCancelled = true;
00186             QApplication::restoreOverrideCursor();
00187         }
00188 
00189         if (!m_graph.isValid())
00190         {
00191             kdError(s_area) << "Couldn't create a valid graph for this source mimetype: "
00192                             << t->name() << endl;
00193             importErrorHelper( t->name(), userCancelled );
00194             status = KoFilter::BadConversionGraph;
00195             return QString::null;
00196         }
00197     }
00198 
00199     KoFilterChain::Ptr chain( 0 );
00200     // Are we owned by a KoDocument?
00201     if ( m_document ) {
00202         QCString mimeType( m_document->nativeFormatMimeType() );
00203         chain = m_graph.chain( this, mimeType );
00204     }
00205     else {
00206         kdError(s_area) << "You aren't supposed to use import() from a filter!" << endl;
00207         status = KoFilter::UsageError;
00208         return QString::null;
00209     }
00210 
00211     if ( !chain ) {
00212         kdError(s_area) << "Couldn't create a valid filter chain!" << endl;
00213         importErrorHelper( t->name() );
00214         status = KoFilter::BadConversionGraph;
00215         return QString::null;
00216     }
00217 
00218     // Okay, let's invoke the filters one after the other
00219     m_direction = Import; // vital information!
00220     m_importUrl = url;  // We want to load that file
00221     m_exportUrl = QString::null;  // This is null for sure, as embedded stuff isn't
00222                                   // allowed to use that method
00223     status = chain->invokeChain();
00224 
00225     m_importUrl = QString::null;  // Reset the import URL
00226 
00227     if ( status == KoFilter::OK )
00228         return chain->chainOutput();
00229     return QString::null;
00230 }
00231 
00232 KoFilter::ConversionStatus KoFilterManager::exp0rt( const QString& url, QCString& mimeType )
00233 {
00234     bool userCancelled = false;
00235 
00236     // The import url should already be set correctly (null if we have a KoDocument
00237     // file manager and to the correct URL if we have an embedded manager)
00238     m_direction = Export; // vital information!
00239     m_exportUrl = url;
00240 
00241     if ( m_document )
00242         m_graph.setSourceMimeType( m_document->nativeFormatMimeType() );
00243     else if ( !m_importUrlMimetypeHint.isEmpty() ) {
00244         kdDebug(s_area) << "Using the mimetype hint: '" << m_importUrlMimetypeHint << "'" << endl;
00245         m_graph.setSourceMimeType( m_importUrlMimetypeHint );
00246     }
00247     else {
00248         KURL u;
00249         u.setPath( m_importUrl );
00250         KMimeType::Ptr t = KMimeType::findByURL( u, 0, true );
00251         if ( t->name() == KMimeType::defaultMimeType() ) {
00252             kdError(s_area) << "No mimetype found for " << m_importUrl << endl;
00253             return KoFilter::BadMimeType;
00254         }
00255         m_graph.setSourceMimeType( t->name().latin1() );
00256 
00257         if ( !m_graph.isValid() ) {
00258             kdWarning(s_area) << "Can't open " << t->name () << ", trying filter chooser" << endl;
00259 
00260             QApplication::setOverrideCursor( arrowCursor );
00261             KoFilterChooser chooser(0, KoFilterManager::mimeFilter ());
00262             if (chooser.exec ())
00263                 m_graph.setSourceMimeType (chooser.filterSelected ().latin1 ());
00264             else
00265                 userCancelled = true;
00266 
00267             QApplication::restoreOverrideCursor();
00268         }
00269     }
00270 
00271     if (!m_graph.isValid ())
00272     {
00273         kdError(s_area) << "Couldn't create a valid graph for this source mimetype." << endl;
00274         if (!userCancelled) KMessageBox::error( 0L, i18n("Could not export file."), i18n("Missing Export Filter") );
00275         return KoFilter::BadConversionGraph;
00276     }
00277 
00278     KoFilterChain::Ptr chain = m_graph.chain( this, mimeType );
00279 
00280     if ( !chain ) {
00281         kdError(s_area) << "Couldn't create a valid filter chain!" << endl;
00282         KMessageBox::error( 0L, i18n("Could not export file."), i18n("Missing Export Filter") );
00283         return KoFilter::BadConversionGraph;
00284     }
00285 
00286     return chain->invokeChain();
00287 }
00288 
00289 namespace  // in order not to mess with the global namespace ;)
00290 {
00291     // This class is needed only for the static mimeFilter method
00292     class Vertex
00293     {
00294     public:
00295         Vertex( const QCString& mimeType ) : m_color( White ), m_mimeType( mimeType ) {}
00296 
00297         enum Color { White, Gray, Black };
00298         Color color() const { return m_color; }
00299         void setColor( Color color ) { m_color = color; }
00300 
00301         QCString mimeType() const { return m_mimeType; }
00302 
00303         void addEdge( Vertex* vertex ) { if ( vertex ) m_edges.append( vertex ); }
00304         QPtrList<Vertex> edges() const { return m_edges; }
00305 
00306     private:
00307         Color m_color;
00308         QCString m_mimeType;
00309         QPtrList<Vertex> m_edges;
00310     };
00311 
00312     // Some helper methods for the static stuff
00313     // This method builds up the graph in the passed ascii dict
00314     void buildGraph( QAsciiDict<Vertex>& vertices, KoFilterManager::Direction direction )
00315     {
00316         QStringList stopList; // Lists of mimetypes that are considered end of chains
00317         stopList << "text/plain";
00318         stopList << "text/csv";
00319         stopList << "text/x-tex";
00320         stopList << "text/html";
00321 
00322         vertices.setAutoDelete( true );
00323 
00324         // partly copied from build graph, but I don't see any other
00325         // way without crude hacks, as we have to obey the direction here
00326         QValueList<KoDocumentEntry> parts( KoDocumentEntry::query(false, QString::null) );
00327         QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
00328         QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );
00329 
00330         while ( partIt != partEnd ) {
00331             QCString key( ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString().latin1() );
00332             if ( !key.isEmpty() )
00333                 vertices.insert( key, new Vertex( key ) );
00334             ++partIt;
00335         }
00336 
00337         QValueList<KoFilterEntry::Ptr> filters = KoFilterEntry::query(); // no constraint here - we want *all* :)
00338         QValueList<KoFilterEntry::Ptr>::ConstIterator it = filters.begin();
00339         QValueList<KoFilterEntry::Ptr>::ConstIterator end = filters.end();
00340 
00341         for ( ; it != end; ++it ) {
00342 
00343             QStringList impList; // Import list
00344             QStringList expList; // Export list
00345 
00346             QStringList::Iterator stopEnd = stopList.end();
00347             // Now we have to exclude the "stop" mimetypes (in the right direction!)
00348             if ( direction == KoFilterManager::Import ) {
00349                 // Import: "stop" mime type should not appear in export
00350                 QStringList::ConstIterator testIt = ( *it )->export_.begin();
00351                 QStringList::ConstIterator testEnd = ( *it )->export_.end();
00352                 for ( ; testIt != testEnd ; ++testIt ) {
00353                     if ( stopList.find( *testIt ) == stopEnd ) {
00354                         expList.append( *testIt );
00355                     }
00356                 }
00357                 impList = ( *it )->import;
00358             }
00359             else {
00360                 // Export: "stop" mime type should not appear in import
00361                 QStringList::ConstIterator testIt = ( *it )->import.begin();
00362                 QStringList::ConstIterator testEnd = ( *it )->import.end();
00363                 for ( ; testIt != testEnd ; ++testIt ) {
00364                     if ( stopList.find( *testIt ) == stopEnd ) {
00365                         impList.append( *testIt );
00366                     }
00367                 }
00368                 expList = ( *it )->export_;
00369             }
00370 
00371             if ( impList.empty() || expList.empty() )
00372             {
00373                 // This filter cannot be used under these conditions
00374                 kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " ruled out" << endl;
00375                 continue;
00376             }
00377 
00378             // First add the "starting points" to the dict
00379             QStringList::ConstIterator importIt = impList.begin();
00380             QStringList::ConstIterator importEnd = impList.end();
00381             for ( ; importIt != importEnd; ++importIt ) {
00382                 const QCString key = ( *importIt ).latin1();  // latin1 is okay here (werner)
00383                 // already there?
00384                 if ( !vertices[ key ] )
00385                     vertices.insert( key, new Vertex( key ) );
00386             }
00387 
00388             // Are we allowed to use this filter at all?
00389             if ( KoFilterManager::filterAvailable( *it ) ) {
00390                 QStringList::ConstIterator exportIt = expList.begin();
00391                 QStringList::ConstIterator exportEnd = expList.end();
00392 
00393                 for ( ; exportIt != exportEnd; ++exportIt ) {
00394                     // First make sure the export vertex is in place
00395                     const QCString key = ( *exportIt ).latin1();  // latin1 is okay here
00396                     Vertex* exp = vertices[ key ];
00397                     if ( !exp ) {
00398                         exp = new Vertex( key );
00399                         vertices.insert( key, exp );
00400                     }
00401                     // Then create the appropriate edges depending on the
00402                     // direction (import/export)
00403                     // This is the chunk of code which actually differs from the
00404                     // graph stuff (apart from the different vertex class)
00405                     importIt = impList.begin(); // ### TODO: why only the first one?
00406                     if ( direction == KoFilterManager::Import ) {
00407                         for ( ; importIt != importEnd; ++importIt )
00408                             exp->addEdge( vertices[ ( *importIt ).latin1() ] );
00409                     } else {
00410                         for ( ; importIt != importEnd; ++importIt )
00411                             vertices[ ( *importIt ).latin1() ]->addEdge( exp );
00412                     }
00413                 }
00414             }
00415             else
00416                 kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " does not apply." << endl;
00417         }
00418     }
00419 
00420     // This method runs a BFS on the graph to determine the connected
00421     // nodes. Make sure that the graph is "cleared" (the colors of the
00422     // nodes are all white)
00423     QStringList connected( const QAsciiDict<Vertex>& vertices, const QCString& mimetype )
00424     {
00425         if ( mimetype.isEmpty() )
00426             return QStringList();
00427         Vertex *v = vertices[ mimetype ];
00428         if ( !v )
00429             return QStringList();
00430 
00431         v->setColor( Vertex::Gray );
00432         std::queue<Vertex*> queue;
00433         queue.push( v );
00434         QStringList connected;
00435 
00436         while ( !queue.empty() ) {
00437             v = queue.front();
00438             queue.pop();
00439             QPtrList<Vertex> edges = v->edges();
00440             QPtrListIterator<Vertex> it( edges );
00441             for ( ; it.current(); ++it ) {
00442                 if ( it.current()->color() == Vertex::White ) {
00443                     it.current()->setColor( Vertex::Gray );
00444                     queue.push( it.current() );
00445                 }
00446             }
00447             v->setColor( Vertex::Black );
00448             connected.append( v->mimeType() );
00449         }
00450         return connected;
00451     }
00452 }
00453 
00454 // The static method to figure out to which parts of the
00455 // graph this mimetype has a connection to.
00456 QStringList KoFilterManager::mimeFilter( const QCString& mimetype, Direction direction, const QStringList& extraNativeMimeTypes )
00457 {
00458     QAsciiDict<Vertex> vertices;
00459     buildGraph( vertices, direction );
00460     QStringList lst = connected( vertices, mimetype );
00461     QStringList::Iterator mimeFilterIt = lst.at( 1 );
00462     for( QStringList::ConstIterator extrait = extraNativeMimeTypes.begin(); extrait != extraNativeMimeTypes.end(); ++extrait ) {
00463         mimeFilterIt = lst.insert( mimeFilterIt, *extrait );
00464         ++mimeFilterIt;
00465     }
00466     return lst;
00467 }
00468 
00469 QStringList KoFilterManager::mimeFilter()
00470 {
00471     QAsciiDict<Vertex> vertices;
00472     buildGraph( vertices, KoFilterManager::Import );
00473 
00474     QValueList<KoDocumentEntry> parts( KoDocumentEntry::query(false, QString::null) );
00475     QValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
00476     QValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );
00477 
00478     if ( partIt == partEnd )
00479         return QStringList();
00480 
00481     // To find *all* reachable mimetypes, we have to resort to
00482     // a small hat trick, in order to avoid multiple searches:
00483     // We introduce a fake vertex, which is connected to every
00484     // single KOffice mimetype. Due to that one BFS is enough :)
00485     // Now we just need an... ehrm.. unique name for our fake mimetype
00486     Vertex *v = new Vertex( "supercalifragilistic/x-pialadocious" );
00487     vertices.insert( "supercalifragilistic/x-pialadocious", v );
00488     while ( partIt != partEnd ) {
00489         QCString key( ( *partIt ).service()->property( "X-KDE-NativeMimeType" ).toString().latin1() );
00490         if ( !key.isEmpty() )
00491             v->addEdge( vertices[ key ] );
00492         ++partIt;
00493     }
00494     QStringList result = connected( vertices, "supercalifragilistic/x-pialadocious" );
00495 
00496     // Finally we have to get rid of our fake mimetype again
00497     result.remove( "supercalifragilistic/x-pialadocious" );
00498     return result;
00499 }
00500 
00501 // Here we check whether the filter is available. This stuff is quite slow,
00502 // but I don't see any other convenient (for the user) way out :}
00503 bool KoFilterManager::filterAvailable( KoFilterEntry::Ptr entry )
00504 {
00505     if ( !entry )
00506         return false;
00507     if ( entry->available != "check" )
00508         return true;
00509 
00510     //kdDebug( 30500 ) << "Checking whether " << entry->service()->name() << " applies." << endl;
00511     // generate some "unique" key
00512     QString key( entry->service()->name() );
00513     key += " - ";
00514     key += entry->service()->library();
00515 
00516     if ( !m_filterAvailable.contains( key ) ) {
00517         //kdDebug( 30500 ) << "Not cached, checking..." << endl;
00518 
00519         KLibrary* library = KLibLoader::self()->library( QFile::encodeName( entry->service()->library() ) );
00520         if ( !library ) {
00521             kdWarning( 30500 ) << "Huh?? Couldn't load the lib: "
00522                                << KLibLoader::self()->lastErrorMessage() << endl;
00523             m_filterAvailable[ key ] = false;
00524             return false;
00525         }
00526 
00527         // This code is "borrowed" from klibloader ;)
00528         QCString symname;
00529         symname.sprintf("check_%s", library->name().latin1() );
00530         void* sym = library->symbol( symname );
00531         if ( !sym )
00532         {
00533             kdWarning( 30500 ) << "The library " << library->name()
00534                                << " does not offer a check_" << library->name()
00535                                << " function." << endl;
00536             m_filterAvailable[ key ] = false;
00537         }
00538         else {
00539             typedef int (*t_func)();
00540             t_func check = (t_func)sym;
00541             m_filterAvailable[ key ] = check() == 1;
00542         }
00543     }
00544     return m_filterAvailable[ key ];
00545 }
00546 
00547 void KoFilterManager::importErrorHelper( const QString& mimeType, const bool suppressDialog )
00548 {
00549     QString tmp = i18n("Could not import file of type\n%1").arg( mimeType );
00550     // ###### FIXME: use KLibLoader::lastErrorMessage() here
00551     if (!suppressDialog) KMessageBox::error( 0L, tmp, i18n("Missing Import Filter") );
00552 }
00553 
00554 void KoFilterManager::setBatchMode( const bool batch )
00555 {
00556     d->m_batch = batch;
00557 }
00558 
00559 bool KoFilterManager::getBatchMode( void ) const
00560 {
00561     return d->m_batch;
00562 }
00563 
00564 #include <koFilterManager.moc>
00565 #include <koFilterManager_p.moc>
KDE Logo
This file is part of the documentation for lib Library Version 1.4.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Mon Feb 13 09:40:00 2006 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003