lib

KoFilterManager.cpp
1/* This file is part of the KDE project
2 Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
3 2000, 2001 Werner Trobin <trobin@kde.org>
4 Copyright (C) 2004 Nicolas Goutte <goutte@kde.org>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20*/
21
22
23#include <KoFilterManager.h>
24#include <KoFilterManager_p.h>
25
26#include <tqfile.h>
27#include <tqlabel.h>
28#include <tqlayout.h>
29#include <tqptrlist.h>
30#include <tqapplication.h>
31
32#include <tdelocale.h>
33#include <tdemessagebox.h>
34#include <KoDocument.h>
35#include <klibloader.h>
36#include <tdelistbox.h>
37#include <kmimetype.h>
38#include <kdebug.h>
39
40#include <queue>
41
42#include <unistd.h>
43
44class KoFilterManager::Private
45{
46public:
47 bool m_batch;
48};
49
50
51KoFilterChooser::KoFilterChooser (TQWidget *parent, const TQStringList &mimeTypes, const TQString &nativeFormat)
52 : KDialogBase (parent, "kofilterchooser", true, i18n ("Choose Filter"),
53 KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true),
54 m_mimeTypes (mimeTypes)
55{
56 setInitialSize (TQSize (300, 350));
57
58 TQWidget *page = new TQWidget (this);
59 setMainWidget (page);
60
61 // looks too squashed together without * 2
62 TQVBoxLayout *layout = new TQVBoxLayout (page, marginHint (), spacingHint () * 2);
63
64 TQLabel *filterLabel = new TQLabel (i18n ("Select a filter:"), page, "filterlabel");
65 layout->addWidget (filterLabel);
66
67 m_filterList = new TDEListBox (page, "filterlist");
68 layout->addWidget (m_filterList);
69
70 Q_ASSERT (!m_mimeTypes.isEmpty ());
71 for (TQStringList::ConstIterator it = m_mimeTypes.begin ();
72 it != m_mimeTypes.end ();
73 it++)
74 {
75 KMimeType::Ptr mime = KMimeType::mimeType (*it);
76 m_filterList->insertItem (mime->comment ());
77 }
78
79 if (nativeFormat == "application/x-kword")
80 {
81 const int index = m_mimeTypes.findIndex ("text/plain");
82 if (index > -1)
83 m_filterList->setCurrentItem (index);
84 }
85
86 if (m_filterList->currentItem () == -1)
87 m_filterList->setCurrentItem (0);
88
89 m_filterList->centerCurrentItem ();
90 m_filterList->setFocus ();
91
92 connect (m_filterList, TQ_SIGNAL (selected (int)), this, TQ_SLOT (slotOk ()));
93}
94
95KoFilterChooser::~KoFilterChooser ()
96{
97}
98
99TQString KoFilterChooser::filterSelected ()
100{
101 const int item = m_filterList->currentItem ();
102
103 if (item > -1)
104 return m_mimeTypes [item];
105 else
106 return TQString();
107}
108
109
110// static cache for filter availability
111TQMap<TQString, bool> KoFilterManager::m_filterAvailable;
112
113const int KoFilterManager::s_area = 30500;
114
115
117 m_document( document ), m_parentChain( 0 ), m_graph( "" ), d( 0 )
118{
119 d = new KoFilterManager::Private;
120 d -> m_batch = false;
121 if ( document )
122 TQObject::connect( this, TQ_SIGNAL( sigProgress( int ) ),
123 document, TQ_SIGNAL( sigProgress( int ) ) );
124}
125
126
127KoFilterManager::KoFilterManager( const TQString& url, const TQCString& mimetypeHint,
128 KoFilterChain* const parentChain ) :
129 m_document( 0 ), m_parentChain( parentChain ), m_importUrl( url ), m_importUrlMimetypeHint( mimetypeHint ),
130 m_graph( "" ), d( 0 )
131{
132 d = new KoFilterManager::Private;
133 d -> m_batch = false;
134}
135
136KoFilterManager::~KoFilterManager()
137{
138 delete d;
139}
140
141TQString KoFilterManager::import( const TQString& url, KoFilter::ConversionStatus& status )
142{
143 // Find the mime type for the file to be imported.
144 KURL u;
145 u.setPath( url );
146 KMimeType::Ptr t = KMimeType::findByURL( u, 0, true );
147
148 m_graph.setSourceMimeType( t->name().latin1() ); // .latin1() is okay here (Werner)
149 if ( !m_graph.isValid() ) {
150 bool userCancelled = false;
151
152 kdWarning(s_area) << "Can't open " << t->name () << ", trying filter chooser" << endl;
153 if ( m_document )
154 {
155 if ( !m_document->isAutoErrorHandlingEnabled() )
156 {
157 status = KoFilter::BadConversionGraph;
158 return TQString();
159 }
160 TQCString nativeFormat = m_document->nativeFormatMimeType ();
161
162 TQApplication::setOverrideCursor( arrowCursor );
163 KoFilterChooser chooser(0,
164 KoFilterManager::mimeFilter (nativeFormat, KoFilterManager::Import, m_document->extraNativeMimeTypes()),
165 nativeFormat);
166 if (chooser.exec ())
167 {
168 TQCString f = chooser.filterSelected ().latin1();
169
170 if (f == nativeFormat)
171 {
172 status = KoFilter::OK;
173 TQApplication::restoreOverrideCursor();
174 return url;
175 }
176
177 m_graph.setSourceMimeType (f);
178 }
179 else
180 userCancelled = true;
181 TQApplication::restoreOverrideCursor();
182 }
183
184 if (!m_graph.isValid())
185 {
186 kdError(s_area) << "Couldn't create a valid graph for this source mimetype: "
187 << t->name() << endl;
188 importErrorHelper( t->name(), userCancelled );
189 status = KoFilter::BadConversionGraph;
190 return TQString();
191 }
192 }
193
194 KoFilterChain::Ptr chain( 0 );
195 // Are we owned by a KoDocument?
196 if ( m_document ) {
197 TQCString mimeType = m_document->nativeFormatMimeType();
198 TQStringList extraMimes = m_document->extraNativeMimeTypes();
199 int i=0, n = extraMimes.count();
200 chain = m_graph.chain( this, mimeType );
201 while( !chain && i<n) {
202 mimeType = extraMimes[i].utf8();
203 chain = m_graph.chain( this, mimeType );
204 ++i;
205 }
206 }
207 else {
208 kdError(s_area) << "You aren't supposed to use import() from a filter!" << endl;
209 status = KoFilter::UsageError;
210 return TQString();
211 }
212
213 if ( !chain ) {
214 kdError(s_area) << "Couldn't create a valid filter chain!" << endl;
215 importErrorHelper( t->name() );
216 status = KoFilter::BadConversionGraph;
217 return TQString();
218 }
219
220 // Okay, let's invoke the filters one after the other
221 m_direction = Import; // vital information!
222 m_importUrl = url; // We want to load that file
223 m_exportUrl = TQString(); // This is null for sure, as embedded stuff isn't
224 // allowed to use that method
225 status = chain->invokeChain();
226
227 m_importUrl = TQString(); // Reset the import URL
228
229 if ( status == KoFilter::OK )
230 return chain->chainOutput();
231 return TQString();
232}
233
234KoFilter::ConversionStatus KoFilterManager::exp0rt( const TQString& url, TQCString& mimeType )
235{
236 bool userCancelled = false;
237
238 // The import url should already be set correctly (null if we have a KoDocument
239 // file manager and to the correct URL if we have an embedded manager)
240 m_direction = Export; // vital information!
241 m_exportUrl = url;
242
243 KoFilterChain::Ptr chain = 0;
244 if ( m_document ) {
245 // We have to pick the right native mimetype as source.
246 TQStringList nativeMimeTypes;
247 nativeMimeTypes.append( m_document->nativeFormatMimeType() );
248 nativeMimeTypes += m_document->extraNativeMimeTypes();
249 TQStringList::ConstIterator it = nativeMimeTypes.begin();
250 const TQStringList::ConstIterator end = nativeMimeTypes.end();
251 for ( ; !chain && it != end; ++it )
252 {
253 m_graph.setSourceMimeType( (*it).latin1() );
254 if ( m_graph.isValid() )
255 chain = m_graph.chain( this, mimeType );
256 }
257 }
258 else if ( !m_importUrlMimetypeHint.isEmpty() ) {
259 kdDebug(s_area) << "Using the mimetype hint: '" << m_importUrlMimetypeHint << "'" << endl;
260 m_graph.setSourceMimeType( m_importUrlMimetypeHint );
261 }
262 else {
263 KURL u;
264 u.setPath( m_importUrl );
265 KMimeType::Ptr t = KMimeType::findByURL( u, 0, true );
266 if ( t->name() == KMimeType::defaultMimeType() ) {
267 kdError(s_area) << "No mimetype found for " << m_importUrl << endl;
268 return KoFilter::BadMimeType;
269 }
270 m_graph.setSourceMimeType( t->name().latin1() );
271
272 if ( !m_graph.isValid() ) {
273 kdWarning(s_area) << "Can't open " << t->name () << ", trying filter chooser" << endl;
274
275 TQApplication::setOverrideCursor( arrowCursor );
276 KoFilterChooser chooser(0, KoFilterManager::mimeFilter ());
277 if (chooser.exec ())
278 m_graph.setSourceMimeType (chooser.filterSelected ().latin1 ());
279 else
280 userCancelled = true;
281
282 TQApplication::restoreOverrideCursor();
283 }
284 }
285
286 if (!m_graph.isValid ())
287 {
288 kdError(s_area) << "Couldn't create a valid graph for this source mimetype." << endl;
289 if (!userCancelled) KMessageBox::error( 0L, i18n("Could not export file."), i18n("Missing Export Filter") );
290 return KoFilter::BadConversionGraph;
291 }
292
293 if ( !chain ) // already set when coming from the m_document case
294 chain = m_graph.chain( this, mimeType );
295
296 if ( !chain ) {
297 kdError(s_area) << "Couldn't create a valid filter chain to " << mimeType << " !" << endl;
298 KMessageBox::error( 0L, i18n("Could not export file."), i18n("Missing Export Filter") );
299 return KoFilter::BadConversionGraph;
300 }
301
302 return chain->invokeChain();
303}
304
305namespace // in order not to mess with the global namespace ;)
306{
307 // This class is needed only for the static mimeFilter method
308 class Vertex
309 {
310 public:
311 Vertex( const TQCString& mimeType ) : m_color( White ), m_mimeType( mimeType ) {}
312
313 enum Color { White, Gray, Black };
314 Color color() const { return m_color; }
315 void setColor( Color color ) { m_color = color; }
316
317 TQCString mimeType() const { return m_mimeType; }
318
319 void addEdge( Vertex* vertex ) { if ( vertex ) m_edges.append( vertex ); }
320 TQPtrList<Vertex> edges() const { return m_edges; }
321
322 private:
323 Color m_color;
324 TQCString m_mimeType;
325 TQPtrList<Vertex> m_edges;
326 };
327
328 // Some helper methods for the static stuff
329 // This method builds up the graph in the passed ascii dict
330 void buildGraph( TQAsciiDict<Vertex>& vertices, KoFilterManager::Direction direction )
331 {
332 TQStringList stopList; // Lists of mimetypes that are considered end of chains
333 stopList << "text/plain";
334 stopList << "text/csv";
335 stopList << "text/x-tex";
336 stopList << "text/html";
337
338 vertices.setAutoDelete( true );
339
340 // partly copied from build graph, but I don't see any other
341 // way without crude hacks, as we have to obey the direction here
342 TQValueList<KoDocumentEntry> parts( KoDocumentEntry::query(false, TQString()) );
343 TQValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
344 TQValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );
345
346 while ( partIt != partEnd ) {
347 TQStringList nativeMimeTypes = ( *partIt ).service()->property( "X-TDE-ExtraNativeMimeTypes" ).toStringList();
348 nativeMimeTypes += ( *partIt ).service()->property( "X-TDE-NativeMimeType" ).toString();
349 TQStringList::ConstIterator it = nativeMimeTypes.begin();
350 const TQStringList::ConstIterator end = nativeMimeTypes.end();
351 for ( ; it != end; ++it )
352 if ( !(*it).isEmpty() )
353 vertices.insert( (*it).latin1(), new Vertex( (*it).latin1() ) );
354 ++partIt;
355 }
356
357 TQValueList<KoFilterEntry::Ptr> filters = KoFilterEntry::query(); // no constraint here - we want *all* :)
358 TQValueList<KoFilterEntry::Ptr>::ConstIterator it = filters.begin();
359 const TQValueList<KoFilterEntry::Ptr>::ConstIterator end = filters.end();
360
361 for ( ; it != end; ++it ) {
362
363 TQStringList impList; // Import list
364 TQStringList expList; // Export list
365
366 const TQStringList::Iterator stopEnd = stopList.end();
367 // Now we have to exclude the "stop" mimetypes (in the right direction!)
368 if ( direction == KoFilterManager::Import ) {
369 // Import: "stop" mime type should not appear in export
370 TQStringList::ConstIterator testIt = ( *it )->export_.begin();
371 TQStringList::ConstIterator testEnd = ( *it )->export_.end();
372 for ( ; testIt != testEnd ; ++testIt ) {
373 if ( stopList.find( *testIt ) == stopEnd ) {
374 expList.append( *testIt );
375 }
376 }
377 impList = ( *it )->import;
378 }
379 else {
380 // Export: "stop" mime type should not appear in import
381 TQStringList::ConstIterator testIt = ( *it )->import.begin();
382 const TQStringList::ConstIterator testEnd = ( *it )->import.end();
383 for ( ; testIt != testEnd ; ++testIt ) {
384 if ( stopList.find( *testIt ) == stopEnd ) {
385 impList.append( *testIt );
386 }
387 }
388 expList = ( *it )->export_;
389 }
390
391 if ( impList.empty() || expList.empty() )
392 {
393 // This filter cannot be used under these conditions
394 kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " ruled out" << endl;
395 continue;
396 }
397
398 // First add the "starting points" to the dict
399 TQStringList::ConstIterator importIt = impList.begin();
400 const TQStringList::ConstIterator importEnd = impList.end();
401 for ( ; importIt != importEnd; ++importIt ) {
402 const TQCString key = ( *importIt ).latin1(); // latin1 is okay here (werner)
403 // already there?
404 if ( !vertices[ key ] )
405 vertices.insert( key, new Vertex( key ) );
406 }
407
408 // Are we allowed to use this filter at all?
410 TQStringList::ConstIterator exportIt = expList.begin();
411 const TQStringList::ConstIterator exportEnd = expList.end();
412
413 for ( ; exportIt != exportEnd; ++exportIt ) {
414 // First make sure the export vertex is in place
415 const TQCString key = ( *exportIt ).latin1(); // latin1 is okay here
416 Vertex* exp = vertices[ key ];
417 if ( !exp ) {
418 exp = new Vertex( key );
419 vertices.insert( key, exp );
420 }
421 // Then create the appropriate edges depending on the
422 // direction (import/export)
423 // This is the chunk of code which actually differs from the
424 // graph stuff (apart from the different vertex class)
425 importIt = impList.begin(); // ### TODO: why only the first one?
426 if ( direction == KoFilterManager::Import ) {
427 for ( ; importIt != importEnd; ++importIt )
428 exp->addEdge( vertices[ ( *importIt ).latin1() ] );
429 } else {
430 for ( ; importIt != importEnd; ++importIt )
431 vertices[ ( *importIt ).latin1() ]->addEdge( exp );
432 }
433 }
434 }
435 else
436 kdDebug( 30500 ) << "Filter: " << ( *it )->service()->name() << " does not apply." << endl;
437 }
438 }
439
440 // This method runs a BFS on the graph to determine the connected
441 // nodes. Make sure that the graph is "cleared" (the colors of the
442 // nodes are all white)
443 TQStringList connected( const TQAsciiDict<Vertex>& vertices, const TQCString& mimetype )
444 {
445 if ( mimetype.isEmpty() )
446 return TQStringList();
447 Vertex *v = vertices[ mimetype ];
448 if ( !v )
449 return TQStringList();
450
451 v->setColor( Vertex::Gray );
452 std::queue<Vertex*> queue;
453 queue.push( v );
454 TQStringList connected;
455
456 while ( !queue.empty() ) {
457 v = queue.front();
458 queue.pop();
459 TQPtrList<Vertex> edges = v->edges();
460 TQPtrListIterator<Vertex> it( edges );
461 for ( ; it.current(); ++it ) {
462 if ( it.current()->color() == Vertex::White ) {
463 it.current()->setColor( Vertex::Gray );
464 queue.push( it.current() );
465 }
466 }
467 v->setColor( Vertex::Black );
468 connected.append( v->mimeType() );
469 }
470 return connected;
471 }
472}
473
474// The static method to figure out to which parts of the
475// graph this mimetype has a connection to.
476TQStringList KoFilterManager::mimeFilter( const TQCString& mimetype, Direction direction, const TQStringList& extraNativeMimeTypes )
477{
478 //kdDebug(s_area) << "mimetype=" << mimetype << " extraNativeMimeTypes=" << extraNativeMimeTypes << endl;
479 TQAsciiDict<Vertex> vertices;
480 buildGraph( vertices, direction );
481
482 // TODO maybe use the fake vertex trick from the method below, to make the search faster?
483
484 TQStringList nativeMimeTypes;
485 nativeMimeTypes.append( TQString::fromLatin1( mimetype ) );
486 nativeMimeTypes += extraNativeMimeTypes;
487
488 // Add the native mimetypes first so that they are on top.
489 TQStringList lst = nativeMimeTypes;
490
491 // Now look for filters which output each of those natives mimetypes
492 for( TQStringList::ConstIterator natit = nativeMimeTypes.begin(); natit != nativeMimeTypes.end(); ++natit ) {
493 const TQStringList outMimes = connected( vertices, (*natit).latin1() );
494 //kdDebug(s_area) << k_funcinfo << "output formats connected to mime " << *natit << " : " << outMimes << endl;
495 for ( TQStringList::ConstIterator mit = outMimes.begin(); mit != outMimes.end(); ++mit )
496 if ( lst.find( *mit ) == lst.end() ) // append only if not there already.
497 lst.append( *mit );
498 }
499 return lst;
500}
501
503{
504 TQAsciiDict<Vertex> vertices;
505 buildGraph( vertices, KoFilterManager::Import );
506
507 TQValueList<KoDocumentEntry> parts( KoDocumentEntry::query(false, TQString()) );
508 TQValueList<KoDocumentEntry>::ConstIterator partIt( parts.begin() );
509 TQValueList<KoDocumentEntry>::ConstIterator partEnd( parts.end() );
510
511 if ( partIt == partEnd )
512 return TQStringList();
513
514 // To find *all* reachable mimetypes, we have to resort to
515 // a small hat trick, in order to avoid multiple searches:
516 // We introduce a fake vertex, which is connected to every
517 // single KOffice mimetype. Due to that one BFS is enough :)
518 // Now we just need an... ehrm.. unique name for our fake mimetype
519 Vertex *v = new Vertex( "supercalifragilistic/x-pialadocious" );
520 vertices.insert( "supercalifragilistic/x-pialadocious", v );
521 while ( partIt != partEnd ) {
522 TQStringList nativeMimeTypes = ( *partIt ).service()->property( "X-TDE-ExtraNativeMimeTypes" ).toStringList();
523 nativeMimeTypes += ( *partIt ).service()->property( "X-TDE-NativeMimeType" ).toString();
524 TQStringList::ConstIterator it = nativeMimeTypes.begin();
525 const TQStringList::ConstIterator end = nativeMimeTypes.end();
526 for ( ; it != end; ++it )
527 if ( !(*it).isEmpty() )
528 v->addEdge( vertices[ (*it).latin1() ] );
529 ++partIt;
530 }
531 TQStringList result = connected( vertices, "supercalifragilistic/x-pialadocious" );
532
533 // Finally we have to get rid of our fake mimetype again
534 result.remove( "supercalifragilistic/x-pialadocious" );
535 return result;
536}
537
538// Here we check whether the filter is available. This stuff is quite slow,
539// but I don't see any other convenient (for the user) way out :}
540bool KoFilterManager::filterAvailable( KoFilterEntry::Ptr entry )
541{
542 if ( !entry )
543 return false;
544 if ( entry->available != "check" )
545 return true;
546
547 //kdDebug( 30500 ) << "Checking whether " << entry->service()->name() << " applies." << endl;
548 // generate some "unique" key
549 TQString key( entry->service()->name() );
550 key += " - ";
551 key += entry->service()->library();
552
553 if ( !m_filterAvailable.contains( key ) ) {
554 //kdDebug( 30500 ) << "Not cached, checking..." << endl;
555
556 KLibrary* library = KLibLoader::self()->library( TQFile::encodeName( entry->service()->library() ) );
557 if ( !library ) {
558 kdWarning( 30500 ) << "Huh?? Couldn't load the lib: "
559 << KLibLoader::self()->lastErrorMessage() << endl;
560 m_filterAvailable[ key ] = false;
561 return false;
562 }
563
564 // This code is "borrowed" from klibloader ;)
565 TQCString symname;
566 symname.sprintf("check_%s", library->name().latin1() );
567 void* sym = library->symbol( symname );
568 if ( !sym )
569 {
570 kdWarning( 30500 ) << "The library " << library->name()
571 << " does not offer a check_" << library->name()
572 << " function." << endl;
573 m_filterAvailable[ key ] = false;
574 }
575 else {
576 typedef int (*t_func)();
577 t_func check = (t_func)sym;
578 m_filterAvailable[ key ] = check() == 1;
579 }
580 }
581 return m_filterAvailable[ key ];
582}
583
584void KoFilterManager::importErrorHelper( const TQString& mimeType, const bool suppressDialog )
585{
586 TQString tmp = i18n("Could not import file of type\n%1").arg( mimeType );
587 // ###### FIXME: use KLibLoader::lastErrorMessage() here
588 if (!suppressDialog) KMessageBox::error( 0L, tmp, i18n("Missing Import Filter") );
589}
590
591void KoFilterManager::setBatchMode( const bool batch )
592{
593 d->m_batch = batch;
594}
595
597{
598 return d->m_batch;
599}
600
601#include <KoFilterManager.moc>
602#include <KoFilterManager_p.moc>
static void error(TQWidget *parent, const TQString &text, const TQString &caption=TQString::null, int options=Notify)
static TQValueList< KoDocumentEntry > query(const TQString &_constr=TQString())
This function will query tdesycoca to find all available components.
The KOffice document class.
Definition KoDocument.h:61
This class represents a chain of plain KoFilter instances.
static TQValueList< KoFilterEntry::Ptr > query(const TQString &_constr=TQString())
This function will query KDED to find all available filters.
static KOFFICECORE_EXPORT TQStringList mimeFilter(const TQCString &mimetype, Direction direction, const TQStringList &extraNativeMimeTypes=TQStringList())
Suitable for passing to KFileDialog::setMimeFilter.
Direction
This enum is used to distinguish the import/export cases.
static TQStringList mimeFilter()
The same method as KoFilterManager::mimeFilter but suited for KoShell.
void setBatchMode(const bool batch)
Set the filter manager is batch mode (no dialog shown) instead of the interactive mode (dialog shown)...
static bool filterAvailable(KoFilterEntry::Ptr entry)
Method used to check if that filter is available at all.
TQString import(const TQString &url, KoFilter::ConversionStatus &status)
Imports the passed URL and returns the resultant filename (most likely some file in /tmp).
bool getBatchMode(void) const
Get if the filter manager is batch mode (true) or in interactive mode (true).
KoFilter::ConversionStatus exp0rt(const TQString &url, TQCString &mimeType)
Exports the given file/document to the specified URL/mimetype.
KoFilterManager(KoDocument *document)
Create a filter manager for a document.
ConversionStatus
This enum is used to signal the return state of your filter.
Definition KoFilter.h:64