File indexing completed on 2024-10-06 04:26:02

0001 /*
0002     SPDX-FileCopyrightText: 1998-2007 Sebastian Trueg <trueg@k3b.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "k3bprojectmanager.h"
0007 
0008 #include <config-k3b.h>
0009 #include "k3bapplication.h"
0010 #include "k3binteractiondialog.h"
0011 #include "k3baudiodoc.h"
0012 #include "k3baudiodatasourceiterator.h"
0013 #include "k3baudiocdtracksource.h"
0014 #include "k3baudioprojectinterface.h"
0015 #include "k3bdatadoc.h"
0016 #include "k3bdataprojectinterface.h"
0017 #include "k3bvideodvddoc.h"
0018 #include "k3bmixeddoc.h"
0019 #include "k3bmixedprojectinterface.h"
0020 #include "k3bvcddoc.h"
0021 #include "k3bmovixdoc.h"
0022 #include "k3bglobals.h"
0023 #include "k3bisooptions.h"
0024 #include "k3bdevicemanager.h"
0025 #include "k3bprojectinterface.h"
0026 #include <KoStore.h>
0027 #include <KoStoreDevice.h>
0028 
0029 #include <KConfig>
0030 #include <KSharedConfig>
0031 #include <KLocalizedString>
0032 #include <KIO/StoredTransferJob>
0033 #include <KIO/FileCopyJob>
0034 #include <KMessageBox>
0035 
0036 #include <QDebug>
0037 #include <QFile>
0038 #include <QHash>
0039 #include <QList>
0040 #include <QTemporaryFile>
0041 #include <QTextStream>
0042 #include <QUrl>
0043 #include <QDomDocument>
0044 #include <QDomElement>
0045 #include <QCursor>
0046 #include <QApplication>
0047 
0048 namespace
0049 {
0050     typedef QHash<K3b::Doc*, K3b::ProjectInterface*> ProjectInterfaces;
0051 
0052     K3b::ProjectInterface* createProjectInterface( K3b::Doc* doc )
0053     {
0054         if( K3b::AudioDoc* audioDoc = dynamic_cast<K3b::AudioDoc*>( doc ) )
0055             return new K3b::AudioProjectInterface( audioDoc );
0056         else if( K3b::DataDoc* dataDoc = dynamic_cast<K3b::DataDoc*>( doc ) )
0057             return new K3b::DataProjectInterface( dataDoc );
0058         else if( K3b::MixedDoc* mixedDoc = dynamic_cast<K3b::MixedDoc*>( doc ) )
0059             return new K3b::MixedProjectInterface( mixedDoc );
0060         else
0061             return new K3b::ProjectInterface( doc );
0062     }
0063 }
0064 
0065 class K3b::ProjectManager::Private
0066 {
0067 public:
0068     QList<Doc*> projects;
0069     Doc* activeProject;
0070     ProjectInterfaces projectInterfaces;
0071 
0072     int audioUntitledCount;
0073     int dataUntitledCount;
0074     int mixedUntitledCount;
0075     int vcdUntitledCount;
0076     int movixUntitledCount;
0077     int videoDvdUntitledCount;
0078 };
0079 
0080 
0081 
0082 
0083 K3b::ProjectManager::ProjectManager( QObject* parent )
0084     : QObject( parent )
0085 {
0086     d = new Private();
0087     d->activeProject = 0;
0088 
0089     d->audioUntitledCount = 0;
0090     d->dataUntitledCount = 0;
0091     d->mixedUntitledCount = 0;
0092     d->vcdUntitledCount = 0;
0093     d->movixUntitledCount = 0;
0094     d->videoDvdUntitledCount = 0;
0095 }
0096 
0097 K3b::ProjectManager::~ProjectManager()
0098 {
0099     delete d;
0100 }
0101 
0102 
0103 QList<K3b::Doc*> K3b::ProjectManager::projects() const
0104 {
0105     return d->projects;
0106 }
0107 
0108 
0109 void K3b::ProjectManager::addProject( K3b::Doc* doc )
0110 {
0111     qDebug() << doc;
0112 
0113     if( !d->projects.contains( doc ) ) {
0114         qDebug() << "adding doc " << doc->URL().toLocalFile();
0115 
0116         d->projects.append( doc );
0117         d->projectInterfaces.insert( doc, createProjectInterface( doc ) );
0118 
0119         connect( doc, SIGNAL(changed(K3b::Doc*)),
0120                  this, SLOT(slotProjectChanged(K3b::Doc*)) );
0121 
0122         emit newProject( doc );
0123     }
0124 }
0125 
0126 
0127 void K3b::ProjectManager::removeProject( K3b::Doc* docRemove )
0128 {
0129     //
0130     // QPtrList.findRef seems to be buggy. Every time we search for the
0131     // first added item it is not found!
0132     //
0133     Q_FOREACH( K3b::Doc* doc, d->projects ) {
0134         if( docRemove == doc ) {
0135             ProjectInterfaces::iterator interface = d->projectInterfaces.find( doc );
0136             if( interface != d->projectInterfaces.end() ) {
0137                 delete interface.value();
0138                 d->projectInterfaces.erase( interface );
0139             }
0140             d->projects.removeAll(doc);
0141             emit closingProject(doc);
0142 
0143             return;
0144         }
0145     }
0146     qDebug() << "(K3b::ProjectManager) unable to find doc: " << docRemove->URL().toLocalFile();
0147 }
0148 
0149 
0150 K3b::Doc* K3b::ProjectManager::findByUrl( const QUrl& url )
0151 {
0152     Q_FOREACH( K3b::Doc* doc, d->projects ) {
0153         if( doc->URL() == url )
0154             return doc;
0155     }
0156     return 0;
0157 }
0158 
0159 
0160 bool K3b::ProjectManager::isEmpty() const
0161 {
0162     return d->projects.isEmpty();
0163 }
0164 
0165 
0166 void K3b::ProjectManager::setActive( K3b::Doc* docActive )
0167 {
0168     if( !docActive ) {
0169         d->activeProject = 0L;
0170         emit activeProjectChanged( 0L );
0171         return;
0172     }
0173 
0174     //
0175     // QPtrList.findRef seems to be buggy. Every time we search for the
0176     // first added item it is not found!
0177     //
0178     Q_FOREACH( K3b::Doc* doc, d->projects ) {
0179         if( docActive == doc ) {
0180             d->activeProject = doc;
0181             emit activeProjectChanged(doc);
0182         }
0183     }
0184 }
0185 
0186 
0187 K3b::Doc* K3b::ProjectManager::activeProject() const
0188 {
0189     return d->activeProject;
0190 }
0191 
0192 
0193 K3b::Doc* K3b::ProjectManager::createEmptyProject( K3b::Doc::Type type )
0194 {
0195     qDebug() << type;
0196 
0197     K3b::Doc* doc = 0;
0198     QString fileName;
0199 
0200     switch( type ) {
0201     case K3b::Doc::AudioProject: {
0202         doc = new K3b::AudioDoc( this );
0203         fileName = i18n("AudioCD%1",d->audioUntitledCount++);
0204         break;
0205     }
0206 
0207     case K3b::Doc::DataProject: {
0208         doc = new K3b::DataDoc( this );
0209         fileName = i18n("Data%1",d->dataUntitledCount++);
0210         break;
0211     }
0212 
0213     case K3b::Doc::MixedProject: {
0214         doc = new K3b::MixedDoc( this );
0215         fileName=i18n("MixedCD%1",d->mixedUntitledCount++);
0216         break;
0217     }
0218 
0219     case K3b::Doc::VcdProject: {
0220         doc = new K3b::VcdDoc( this );
0221         fileName=i18n("VideoCD%1",d->vcdUntitledCount++);
0222         break;
0223     }
0224 
0225     case K3b::Doc::MovixProject: {
0226         doc = new K3b::MovixDoc( this );
0227         fileName=i18n("eMovix%1",d->movixUntitledCount++);
0228         break;
0229     }
0230 
0231 #ifdef ENABLE_DVD_RIPPING
0232     case K3b::Doc::VideoDvdProject: {
0233         doc = new K3b::VideoDvdDoc( this );
0234         fileName = i18n("VideoDVD%1",d->videoDvdUntitledCount++);
0235         break;
0236     }
0237 #endif
0238     }
0239 
0240     doc->setURL(QUrl::fromLocalFile(fileName));
0241 
0242     doc->newDocument();
0243 
0244     loadDefaults( doc );
0245 
0246     return doc;
0247 }
0248 
0249 
0250 K3b::Doc* K3b::ProjectManager::createProject( K3b::Doc::Type type )
0251 {
0252     qDebug() << type;
0253 
0254     K3b::Doc* doc = createEmptyProject( type );
0255 
0256     addProject( doc );
0257 
0258     return doc;
0259 }
0260 
0261 
0262 void K3b::ProjectManager::loadDefaults( K3b::Doc* doc )
0263 {
0264     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0265 
0266     QString cg = "default " + doc->typeString() + " settings";
0267 
0268     // earlier K3b versions loaded the saved settings
0269     // so that is what we do as a default
0270     int i = KConfigGroup( config, QStringLiteral("General Options") ).readEntry( "action dialog startup settings",
0271                                                                                  int(K3b::InteractionDialog::LOAD_SAVED_SETTINGS) );
0272     if( i == K3b::InteractionDialog::LOAD_K3B_DEFAULTS )
0273         return; // the default k3b settings are the ones everyone starts with
0274     else if( i == K3b::InteractionDialog::LOAD_LAST_SETTINGS )
0275         cg = "last used " + cg;
0276     KConfigGroup c(config,cg);
0277 
0278     QString mode = c.readEntry( "writing_mode" );
0279     if ( mode == "dao" )
0280         doc->setWritingMode( K3b::WritingModeSao );
0281     else if( mode == "tao" )
0282         doc->setWritingMode( K3b::WritingModeTao );
0283     else if( mode == "raw" )
0284         doc->setWritingMode( K3b::WritingModeRaw );
0285     else
0286         doc->setWritingMode( K3b::WritingModeAuto );
0287 
0288     doc->setDummy( c.readEntry( "simulate", false ) );
0289     doc->setOnTheFly( c.readEntry( "on_the_fly", true ) );
0290     doc->setRemoveImages( c.readEntry( "remove_image", true ) );
0291     doc->setOnlyCreateImages( c.readEntry( "only_create_image", false ) );
0292     doc->setBurner( k3bcore->deviceManager()->findDevice( c.readEntry( "writer_device" ) ) );
0293     // Default = 0 (Auto)
0294     doc->setSpeed( c.readEntry( "writing_speed", 0 ) );
0295     doc->setWritingApp( K3b::writingAppFromString( c.readEntry( "writing_app" ) ) );
0296 
0297 
0298     switch( doc->type() ) {
0299     case K3b::Doc::AudioProject: {
0300         K3b::AudioDoc* audioDoc = static_cast<K3b::AudioDoc*>(doc);
0301 
0302         audioDoc->writeCdText( c.readEntry( "cd_text", true ) );
0303         audioDoc->setHideFirstTrack( c.readEntry( "hide_first_track", false ) );
0304         audioDoc->setNormalize( c.readEntry( "normalize", false ) );
0305         audioDoc->setAudioRippingParanoiaMode( c.readEntry( "paranoia mode", 0 ) );
0306         audioDoc->setAudioRippingRetries( c.readEntry( "read retries", 128 ) );
0307         audioDoc->setAudioRippingIgnoreReadErrors( c.readEntry( "ignore read errors", false ) );
0308 
0309         break;
0310     }
0311 
0312     case K3b::Doc::MovixProject: {
0313         K3b::MovixDoc* movixDoc = static_cast<K3b::MovixDoc*>(doc);
0314 
0315         movixDoc->setSubtitleFontset( c.readEntry("subtitle_fontset") );
0316 
0317         movixDoc->setLoopPlaylist( c.readEntry("loop", 1 ) );
0318         movixDoc->setAdditionalMPlayerOptions( c.readEntry( "additional_mplayer_options" ) );
0319         movixDoc->setUnwantedMPlayerOptions( c.readEntry( "unwanted_mplayer_options" ) );
0320 
0321         movixDoc->setBootMessageLanguage( c.readEntry("boot_message_language") );
0322 
0323         movixDoc->setDefaultBootLabel( c.readEntry( "default_boot_label" ) );
0324 
0325         movixDoc->setShutdown( c.readEntry( "shutdown", false) );
0326         movixDoc->setReboot( c.readEntry( "reboot", false ) );
0327         movixDoc->setEjectDisk( c.readEntry( "eject", false ) );
0328         movixDoc->setRandomPlay( c.readEntry( "random_play", false ) );
0329         movixDoc->setNoDma( c.readEntry( "no_dma", false ) );
0330 
0331         Q_FALLTHROUGH();
0332     }
0333 
0334     case K3b::Doc::DataProject: {
0335         K3b::DataDoc* dataDoc = static_cast<K3b::DataDoc*>(doc);
0336 
0337         dataDoc->setIsoOptions( K3b::IsoOptions::load( c, false ) );
0338 
0339         QString datamode = c.readEntry( "data_track_mode" );
0340         if( datamode == "mode1" )
0341             dataDoc->setDataMode( K3b::DataMode1 );
0342         else if( datamode == "mode2" )
0343             dataDoc->setDataMode( K3b::DataMode2 );
0344         else
0345             dataDoc->setDataMode( K3b::DataModeAuto );
0346 
0347         dataDoc->setVerifyData( c.readEntry( "verify data", false ) );
0348 
0349         QString s = c.readEntry( "multisession mode" );
0350         if( s == "none" )
0351             dataDoc->setMultiSessionMode( K3b::DataDoc::NONE );
0352         else if( s == "start" )
0353             dataDoc->setMultiSessionMode( K3b::DataDoc::START );
0354         else if( s == "continue" )
0355             dataDoc->setMultiSessionMode( K3b::DataDoc::CONTINUE );
0356         else if( s == "finish" )
0357             dataDoc->setMultiSessionMode( K3b::DataDoc::FINISH );
0358         else
0359             dataDoc->setMultiSessionMode( K3b::DataDoc::AUTO );
0360 
0361         break;
0362     }
0363 
0364     case K3b::Doc::VideoDvdProject: {
0365         // the only defaults we need here are the volume id and stuff
0366         K3b::DataDoc* dataDoc = static_cast<K3b::DataDoc*>(doc);
0367         dataDoc->setIsoOptions( K3b::IsoOptions::load( c, false ) );
0368         dataDoc->setVerifyData( c.readEntry( "verify data", false ) );
0369         break;
0370     }
0371 
0372     case K3b::Doc::MixedProject: {
0373         K3b::MixedDoc* mixedDoc = static_cast<K3b::MixedDoc*>(doc);
0374 
0375         mixedDoc->audioDoc()->writeCdText( c.readEntry( "cd_text", true ) );
0376         mixedDoc->audioDoc()->setNormalize( c.readEntry( "normalize", false ) );
0377 
0378         // load mixed type
0379         if( c.readEntry( "mixed_type" ) == "last_track" )
0380             mixedDoc->setMixedType( K3b::MixedDoc::DATA_LAST_TRACK );
0381         else if( c.readEntry( "mixed_type" ) == "first_track" )
0382             mixedDoc->setMixedType( K3b::MixedDoc::DATA_FIRST_TRACK );
0383         else
0384             mixedDoc->setMixedType( K3b::MixedDoc::DATA_SECOND_SESSION );
0385 
0386         QString datamode = c.readEntry( "data_track_mode" );
0387         if( datamode == "mode1" )
0388             mixedDoc->dataDoc()->setDataMode( K3b::DataMode1 );
0389         else if( datamode == "mode2" )
0390             mixedDoc->dataDoc()->setDataMode( K3b::DataMode2 );
0391         else
0392             mixedDoc->dataDoc()->setDataMode( K3b::DataModeAuto );
0393 
0394         mixedDoc->dataDoc()->setIsoOptions( K3b::IsoOptions::load( c, false ) );
0395 
0396         if( mixedDoc->dataDoc()->isoOptions().volumeID().isEmpty() )
0397             mixedDoc->dataDoc()->setVolumeID( doc->URL().fileName() );
0398 
0399         break;
0400     }
0401 
0402     case K3b::Doc::VcdProject: {
0403         K3b::VcdDoc* vcdDoc = static_cast<K3b::VcdDoc*>(doc);
0404 
0405         // FIXME: I think we miss a lot here!
0406 
0407         vcdDoc->vcdOptions()->setPbcEnabled( c.readEntry( "Use Playback Control", false ) );
0408         vcdDoc->vcdOptions()->setPbcNumkeysEnabled( c.readEntry( "Use numeric keys to navigate chapters", false ) );
0409         vcdDoc->vcdOptions()->setPbcPlayTime( c.readEntry( "Play each Sequence/Segment", 1 ) );
0410         vcdDoc->vcdOptions()->setPbcWaitTime( c.readEntry( "Time to wait after each Sequence/Segment", 2 ) );
0411 
0412         if( vcdDoc->vcdOptions()->volumeId().isEmpty() )
0413             vcdDoc->vcdOptions()->setVolumeId( doc->URL().fileName() );
0414 
0415         break;
0416     }
0417     }
0418 
0419     if( doc->type() == K3b::Doc::DataProject ||
0420         doc->type() == K3b::Doc::MovixProject ||
0421         doc->type() == K3b::Doc::VideoDvdProject ) {
0422         if( static_cast<K3b::DataDoc*>(doc)->isoOptions().volumeID().isEmpty() )
0423             static_cast<K3b::DataDoc*>(doc)->setVolumeID( doc->URL().fileName() );
0424     }
0425 
0426     doc->setModified( false );
0427 }
0428 
0429 
0430 QString K3b::ProjectManager::dbusPath( K3b::Doc* doc ) const
0431 {
0432     ProjectInterfaces::const_iterator it = d->projectInterfaces.constFind( doc );
0433     if( it != d->projectInterfaces.constEnd() )
0434         return it.value()->dbusPath();
0435     else
0436         return QString();
0437 }
0438 
0439 
0440 K3b::Doc* K3b::ProjectManager::openProject( const QUrl& url )
0441 {
0442     QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) );
0443 
0444     QTemporaryFile tmpfile;
0445     tmpfile.setAutoRemove(false);
0446     KIO::StoredTransferJob* transferJob = KIO::storedGet( url );
0447     if (!transferJob->exec())
0448         return NULL;
0449     tmpfile.open();
0450     tmpfile.write(transferJob->data());
0451     tmpfile.close();
0452 
0453     // ///////////////////////////////////////////////
0454     // first check if it's a store or an old plain xml file
0455     bool success = false;
0456     QDomDocument xmlDoc;
0457 
0458     // try opening a store
0459     KoStore* store = KoStore::createStore( tmpfile.fileName(), KoStore::Read );
0460     if( store ) {
0461         if( !store->bad() ) {
0462             // try opening the document inside the store
0463             if( store->open( "maindata.xml" ) ) {
0464                 QIODevice* dev = store->device();
0465                 dev->open( QIODevice::ReadOnly );
0466                 if( xmlDoc.setContent( dev ) )
0467                     success = true;
0468                 dev->close();
0469                 store->close();
0470             }
0471         }
0472 
0473         delete store;
0474     }
0475 
0476     if( !success ) {
0477         // try reading an old plain document
0478         tmpfile.remove();
0479         if ( tmpfile.open() ) {
0480             //
0481             // First check if this is really an xml file because if this is a very big file
0482             // the setContent method blocks for a very long time
0483             //
0484             char test[5];
0485             if( tmpfile.read( test, 5 ) ) {
0486                 if( ::strncmp( test, "<?xml", 5 ) ) {
0487                     qDebug() << "(K3b::Doc) " << url.toLocalFile() << " seems to be no xml file.";
0488                     QApplication::restoreOverrideCursor();
0489                     return 0;
0490                 }
0491                 tmpfile.reset();
0492             }
0493             else {
0494                 qDebug() << "(K3b::Doc) could not read from file.";
0495                 QApplication::restoreOverrideCursor();
0496                 return 0;
0497             }
0498             if( xmlDoc.setContent( &tmpfile ) )
0499                 success = true;
0500             tmpfile.remove();
0501         }
0502     }
0503 
0504     // ///////////////////////////////////////////////
0505     if( !success ) {
0506         qDebug() << "(K3b::Doc) could not open file " << url.toLocalFile();
0507         QApplication::restoreOverrideCursor();
0508         return 0;
0509     }
0510 
0511     // check the documents DOCTYPE
0512     K3b::Doc::Type type = K3b::Doc::AudioProject;
0513     if( xmlDoc.doctype().name() == "k3b_audio_project" )
0514         type = K3b::Doc::AudioProject;
0515     else if( xmlDoc.doctype().name() == "k3b_data_project" )
0516         type = K3b::Doc::DataProject;
0517     else if( xmlDoc.doctype().name() == "k3b_vcd_project" )
0518         type = K3b::Doc::VcdProject;
0519     else if( xmlDoc.doctype().name() == "k3b_mixed_project" )
0520         type = K3b::Doc::MixedProject;
0521     else if( xmlDoc.doctype().name() == "k3b_movix_project" )
0522         type = K3b::Doc::MovixProject;
0523     else if( xmlDoc.doctype().name() == "k3b_movixdvd_project" )
0524         type = K3b::Doc::MovixProject; // backward compatibility
0525     else if( xmlDoc.doctype().name() == "k3b_dvd_project" )
0526         type = K3b::Doc::DataProject; // backward compatibility
0527     else if( xmlDoc.doctype().name() == "k3b_video_dvd_project" ) {
0528         type = K3b::Doc::VideoDvdProject;
0529     } else {
0530         qDebug() << "(K3b::Doc) unknown doc type: " << xmlDoc.doctype().name();
0531         QApplication::restoreOverrideCursor();
0532         return 0;
0533     }
0534 
0535     // we do not know yet if we will be able to actually open the project, so don't inform others yet
0536     K3b::Doc* newDoc = createEmptyProject( type );
0537 
0538     // ---------
0539     // load the data into the document
0540     QDomElement root = xmlDoc.documentElement();
0541     if( newDoc->loadDocumentData( &root ) ) {
0542         newDoc->setURL( url );
0543         newDoc->setSaved( true );
0544         newDoc->setModified( false );
0545 
0546         // ok, finish the doc setup, inform the others about the new project
0547         //dcopInterface( newDoc );
0548         addProject( newDoc );
0549 
0550         // FIXME: find a better way to tell everyone (especially the projecttabwidget)
0551         //        that the doc is not changed
0552         emit projectSaved( newDoc );
0553 
0554         qDebug() << "(K3b::ProjectManager) loading project done.";
0555     }
0556     else {
0557         delete newDoc;
0558         newDoc = 0;
0559     }
0560 
0561     QApplication::restoreOverrideCursor();
0562 
0563     return newDoc;
0564 }
0565 
0566 
0567 bool K3b::ProjectManager::saveProject( K3b::Doc* doc, const QUrl& url )
0568 {
0569     QTemporaryFile tmpfile;
0570     tmpfile.setAutoRemove(false);
0571     tmpfile.open();
0572 
0573     bool success = false;
0574 
0575     // create the store
0576     KoStore* store = KoStore::createStore( tmpfile.fileName(), KoStore::Write, "application/x-k3b" );
0577     if( store ) {
0578         if( store->bad() ) {
0579             delete store;
0580         }
0581         else {
0582             // open the document inside the store
0583             store->open( "maindata.xml" );
0584 
0585             // save the data in the document
0586             QDomDocument xmlDoc( "k3b_" + doc->typeString() + "_project" );
0587 
0588             xmlDoc.appendChild( xmlDoc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) );
0589             QDomElement docElem = xmlDoc.createElement( "k3b_" + doc->typeString() + "_project" );
0590             xmlDoc.appendChild( docElem );
0591             success = doc->saveDocumentData( &docElem );
0592             if( success ) {
0593                 KoStoreDevice dev(store);
0594                 dev.open( QIODevice::WriteOnly );
0595                 QTextStream xmlStream( &dev );
0596                 xmlDoc.save( xmlStream, 0 );
0597 
0598                 doc->setURL( url );
0599                 doc->setModified( false );
0600             }
0601 
0602             // close the document inside the store
0603             store->close();
0604 
0605             // remove the store (destructor writes the store to disk)
0606             delete store;
0607 
0608             doc->setSaved( success );
0609 
0610             if( success ) {
0611                 emit projectSaved( doc );
0612             }
0613         }
0614     }
0615     KIO::FileCopyJob *copyJob = KIO::file_move(QUrl::fromLocalFile(tmpfile.fileName()), url, -1, KIO::Overwrite);
0616     copyJob->exec();
0617 
0618     return success;
0619 }
0620 
0621 
0622 void K3b::ProjectManager::slotProjectChanged( K3b::Doc* doc )
0623 {
0624     emit projectChanged( doc );
0625 }
0626 
0627 #include "moc_k3bprojectmanager.cpp"