File indexing completed on 2024-05-19 04:29:17

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 1998-1999 Torben Weis <weis@kde.org>
0003  * SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
0004  * SPDX-FileCopyrightText: 2007-2008 Thorsten Zachmann <zachmann@kde.org>
0005  * SPDX-FileCopyrightText: 2010-2012 Boudewijn Rempt <boud@valdyas.org>
0006  * SPDX-FileCopyrightText: 2011 Inge Wallin <ingwa@kogmbh.com>
0007  * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com>
0008  *
0009  * SPDX-License-Identifier: LGPL-2.0-or-later
0010  */
0011 
0012 #include "KisPart.h"
0013 
0014 #include <config-mlt.h>
0015 
0016 #include "KoProgressProxy.h"
0017 #include <KoCanvasController.h>
0018 #include <KoCanvasControllerWidget.h>
0019 #include <KoColorSpaceEngine.h>
0020 #include <KoCanvasBase.h>
0021 #include <KoToolManager.h>
0022 #include <KoShapeControllerBase.h>
0023 #include <KoResourceServerProvider.h>
0024 #include <kis_icon.h>
0025 
0026 #include "KisApplication.h"
0027 #include "KisMainWindow.h"
0028 #include "KisDocument.h"
0029 #include "KisView.h"
0030 #include "KisViewManager.h"
0031 #include "KisImportExportManager.h"
0032 #include "KoDocumentInfo.h"
0033 
0034 #include <kis_debug.h>
0035 #include <KoResourcePaths.h>
0036 #include <KoDialog.h>
0037 #include <QMessageBox>
0038 #include <QMenu>
0039 #include <QScopedPointer>
0040 #include <QMap>
0041 
0042 #include <QMenuBar>
0043 #include <klocalizedstring.h>
0044 #include <kactioncollection.h>
0045 #include <kconfig.h>
0046 #include <kconfiggroup.h>
0047 #include <QKeySequence>
0048 
0049 #include <QDialog>
0050 #include <QApplication>
0051 #include <QDomDocument>
0052 #include <QDomElement>
0053 #include <QGlobalStatic>
0054 #include <KisMimeDatabase.h>
0055 #include <dialogs/KisSessionManagerDialog.h>
0056 
0057 #include <kis_group_layer.h>
0058 #include "kis_config.h"
0059 #include "kis_shape_controller.h"
0060 #include "KisResourceServerProvider.h"
0061 #include "kis_animation_cache_populator.h"
0062 #include "kis_image_animation_interface.h"
0063 #include "kis_time_span.h"
0064 #include "kis_idle_watcher.h"
0065 #include "kis_image.h"
0066 #include "KisTranslateLayerNamesVisitor.h"
0067 #include "kis_color_manager.h"
0068 
0069 #include <KisCursorOverrideLock.h>
0070 #include "kis_action.h"
0071 #include "kis_action_registry.h"
0072 #include "KisSessionResource.h"
0073 #include "KisBusyWaitBroker.h"
0074 #include "dialogs/kis_delayed_save_dialog.h"
0075 #include "kis_memory_statistics_server.h"
0076 #include "KisRecentFilesManager.h"
0077 #include "KisRecentFileIconCache.h"
0078 #include "KisPlaybackEngine.h"
0079 #include "KisPlaybackEngineQT.h"
0080 
0081 #ifdef HAVE_MLT
0082 #include "KisPlaybackEngineMLT.h"
0083 #endif
0084 
0085 Q_GLOBAL_STATIC(KisPart, s_instance)
0086 
0087 
0088 class Q_DECL_HIDDEN KisPart::Private
0089 {
0090 public:
0091     Private(KisPart *_part)
0092         : part(_part)
0093         , idleWatcher(2500)
0094         , animationCachePopulator(_part)
0095         , playbackEngine(nullptr)
0096     {
0097     }
0098 
0099     ~Private()
0100     {
0101     }
0102 
0103     KisPart *part;
0104 
0105     QList<QPointer<KisView> > views;
0106     QList<QPointer<KisMainWindow> > mainWindows;
0107     QList<QPointer<KisDocument> > documents;
0108     KisIdleWatcher idleWatcher;
0109     KisAnimationCachePopulator animationCachePopulator;
0110     QScopedPointer<KisPlaybackEngine> playbackEngine;
0111 
0112     KisSessionResourceSP currentSession;
0113     bool closingSession{false};
0114     QScopedPointer<KisSessionManagerDialog> sessionManager;
0115 
0116     QMap<QUrl, QUrl> pendingAddRecentUrlMap;
0117 
0118     bool queryCloseDocument(KisDocument *document) {
0119         Q_FOREACH(auto view, views) {
0120             if (view && view->isVisible() && view->document() == document) {
0121                 return view->queryClose();
0122             }
0123         }
0124 
0125         return true;
0126     }
0127 };
0128 
0129 
0130 KisPart* KisPart::instance()
0131 {
0132     return s_instance;
0133 }
0134 
0135 namespace {
0136 void busyWaitWithFeedback(KisImageSP image)
0137 {
0138     const int busyWaitDelay = 1000;
0139     if (KisPart::instance()->currentMainwindow()) {
0140         KisDelayedSaveDialog dialog(image, KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, KisPart::instance()->currentMainwindow());
0141         dialog.blockIfImageIsBusy();
0142     }
0143 }
0144 }
0145 
0146 KisPart::KisPart()
0147     : d(new Private(this))
0148 {
0149     // Preload all the resources in the background
0150     Q_UNUSED(KoResourceServerProvider::instance());
0151     Q_UNUSED(KisResourceServerProvider::instance());
0152     Q_UNUSED(KisColorManager::instance());
0153 
0154     connect(this, SIGNAL(documentOpened(QString)),
0155             this, SLOT(updateIdleWatcherConnections()));
0156 
0157     connect(this, SIGNAL(documentClosed(QString)),
0158             this, SLOT(updateIdleWatcherConnections()));
0159 
0160     connect(KisActionRegistry::instance(), SIGNAL(shortcutsUpdated()),
0161             this, SLOT(updateShortcuts()));
0162     connect(&d->idleWatcher, SIGNAL(startedIdleMode()),
0163             &d->animationCachePopulator, SLOT(slotRequestRegeneration()));
0164     connect(&d->idleWatcher, SIGNAL(startedIdleMode()),
0165             KisMemoryStatisticsServer::instance(), SLOT(tryForceUpdateMemoryStatisticsWhileIdle()));
0166 
0167     // We start by loading the simple QTimer-based anim playback engine first.
0168     // To save RAM, the MLT-based engine will be loaded later, once the KisImage in question becomes animated.
0169     setPlaybackEngine(new KisPlaybackEngineQT(this));
0170 
0171     d->animationCachePopulator.slotRequestRegeneration();
0172     KisBusyWaitBroker::instance()->setFeedbackCallback(&busyWaitWithFeedback);
0173 }
0174 
0175 KisPart::~KisPart()
0176 {
0177     while (!d->documents.isEmpty()) {
0178         delete d->documents.takeFirst();
0179     }
0180 
0181     while (!d->views.isEmpty()) {
0182         delete d->views.takeFirst();
0183     }
0184 
0185     while (!d->mainWindows.isEmpty()) {
0186         delete d->mainWindows.takeFirst();
0187     }
0188 
0189     delete d;
0190 }
0191 
0192 void KisPart::updateIdleWatcherConnections()
0193 {
0194     QVector<KisImageSP> images;
0195 
0196     Q_FOREACH (QPointer<KisDocument> document, documents()) {
0197         if (document->image()) {
0198             images << document->image();
0199         }
0200     }
0201 
0202     d->idleWatcher.setTrackedImages(images);
0203 
0204     /**
0205      * Update memory stats on changing the amount of images open in Krita
0206      */
0207     d->idleWatcher.forceImageModified();
0208 }
0209 
0210 void KisPart::addDocument(KisDocument *document, bool notify)
0211 {
0212     //dbgUI << "Adding document to part list" << document;
0213     Q_ASSERT(document);
0214     if (!d->documents.contains(document)) {
0215         d->documents.append(document);
0216         if (notify){
0217             emit documentOpened('/'+ objectName());
0218             emit sigDocumentAdded(document);
0219         }
0220         connect(document, SIGNAL(sigSavingFinished(QString)), SLOT(slotDocumentSaved(QString)));
0221     }
0222 }
0223 
0224 QList<QPointer<KisDocument> > KisPart::documents() const
0225 {
0226     return d->documents;
0227 }
0228 
0229 KisDocument *KisPart::createDocument() const
0230 {
0231     KisDocument *doc = new KisDocument();
0232     return doc;
0233 }
0234 
0235 KisDocument *KisPart::createTemporaryDocument() const
0236 {
0237     KisDocument *doc = new KisDocument(false);
0238     return doc;
0239 }
0240 
0241 
0242 int KisPart::documentCount() const
0243 {
0244     return d->documents.size();
0245 }
0246 
0247 void KisPart::removeDocument(KisDocument *document, bool deleteDocument)
0248 {
0249     if (document) {
0250         d->documents.removeAll(document);
0251         emit documentClosed('/' + objectName());
0252         emit sigDocumentRemoved(document->path());
0253         if (deleteDocument) {
0254             document->deleteLater();
0255         }
0256     }
0257 }
0258 
0259 KisMainWindow *KisPart::createMainWindow(QUuid id)
0260 {
0261     KisMainWindow *mw = new KisMainWindow(id);
0262     dbgUI <<"mainWindow" << (void*)mw << "added to view" << this;
0263     d->mainWindows.append(mw);
0264 
0265     // Add all actions with a menu property to the main window
0266     Q_FOREACH(QAction *action, mw->actionCollection()->actions()) {
0267         QString menuLocation = action->property("menulocation").toString();
0268         if (!menuLocation.isEmpty()) {
0269             QAction *found = 0;
0270             QList<QAction *> candidates = mw->menuBar()->actions();
0271             Q_FOREACH(const QString &name, menuLocation.split("/")) {
0272                 Q_FOREACH(QAction *candidate, candidates) {
0273                     if (candidate->objectName().toLower() == name.toLower()) {
0274                         found = candidate;
0275                         candidates = candidate->menu()->actions();
0276                         break;
0277                     }
0278                 }
0279                 if (candidates.isEmpty()) {
0280                     break;
0281                 }
0282             }
0283 
0284             if (found && found->menu()) {
0285                 found->menu()->addAction(action);
0286             }
0287         }
0288     }
0289 
0290 
0291     return mw;
0292 }
0293 
0294 void KisPart::notifyMainWindowIsBeingCreated(KisMainWindow *mainWindow)
0295 {
0296     emit sigMainWindowIsBeingCreated(mainWindow);
0297 }
0298 
0299 
0300 KisView *KisPart::createView(KisDocument *document,
0301                              KisViewManager *viewManager,
0302                              QWidget *parent)
0303 {
0304     // If creating the canvas fails, record this and disable OpenGL next time
0305     KisConfig cfg(false);
0306     KConfigGroup grp( KSharedConfig::openConfig(), "crashprevention");
0307     if (grp.readEntry("CreatingCanvas", false)) {
0308         cfg.disableOpenGL();
0309     }
0310     if (cfg.canvasState() == "OPENGL_FAILED") {
0311         cfg.disableOpenGL();
0312     }
0313     grp.writeEntry("CreatingCanvas", true);
0314     grp.sync();
0315 
0316     KisView *view = nullptr;
0317     {
0318         KisCursorOverrideLock cursorLock(Qt::WaitCursor);
0319         view = new KisView(document,  viewManager, parent);
0320     }
0321 
0322     // Record successful canvas creation
0323     grp.writeEntry("CreatingCanvas", false);
0324     grp.sync();
0325 
0326     addView(view);
0327 
0328     return view;
0329 }
0330 
0331 void KisPart::addView(KisView *view)
0332 {
0333     if (!view)
0334         return;
0335 
0336     if (!d->views.contains(view)) {
0337         d->views.append(view);
0338     }
0339 
0340     emit sigViewAdded(view);
0341 }
0342 
0343 void KisPart::removeView(KisView *view)
0344 {
0345     if (!view) return;
0346 
0347     /**
0348      * HACK ALERT: we check here explicitly if the document (or main
0349      *             window), is saving the stuff. If we close the
0350      *             document *before* the saving is completed, a crash
0351      *             will happen.
0352      */
0353     KIS_ASSERT_RECOVER_RETURN(!view->mainWindow()->hackIsSaving());
0354 
0355     emit sigViewRemoved(view);
0356 
0357     QPointer<KisDocument> doc = view->document();
0358     d->views.removeAll(view);
0359 
0360     if (doc) {
0361         bool found = false;
0362         Q_FOREACH (QPointer<KisView> view, d->views) {
0363             if (view && view->document() == doc) {
0364                 found = true;
0365                 break;
0366             }
0367         }
0368         if (!found) {
0369             removeDocument(doc);
0370         }
0371     }
0372 }
0373 
0374 QList<QPointer<KisView> > KisPart::views() const
0375 {
0376     return d->views;
0377 }
0378 
0379 int KisPart::viewCount(KisDocument *doc) const
0380 {
0381     if (!doc) {
0382         return d->views.count();
0383     }
0384     else {
0385         int count = 0;
0386         Q_FOREACH (QPointer<KisView> view, d->views) {
0387             if (view && view->isVisible() && view->document() == doc) {
0388                 count++;
0389             }
0390         }
0391         return count;
0392     }
0393 }
0394 
0395 bool KisPart::closingSession() const
0396 {
0397     return d->closingSession;
0398 }
0399 
0400 bool KisPart::exists()
0401 {
0402     return s_instance.exists();
0403 }
0404 
0405 bool KisPart::closeSession(bool keepWindows)
0406 {
0407     d->closingSession = true;
0408 
0409     Q_FOREACH(auto document, d->documents) {
0410         if (!d->queryCloseDocument(document.data())) {
0411             d->closingSession = false;
0412             return false;
0413         }
0414     }
0415 
0416     if (d->currentSession) {
0417         KisConfig kisCfg(false);
0418         if (kisCfg.saveSessionOnQuit(false)) {
0419 
0420             d->currentSession->storeCurrentWindows();
0421             KisResourceModel model(ResourceType::Sessions);
0422             bool result = model.updateResource(d->currentSession);
0423             Q_UNUSED(result);
0424 
0425             KConfigGroup cfg = KSharedConfig::openConfig()->group("session");
0426             cfg.writeEntry("previousSession", d->currentSession->name());
0427         }
0428 
0429         d->currentSession = nullptr;
0430     }
0431 
0432     if (!keepWindows) {
0433         Q_FOREACH (auto window, d->mainWindows) {
0434             window->close();
0435         }
0436 
0437         if (d->sessionManager) {
0438             d->sessionManager->close();
0439         }
0440     }
0441 
0442     d->closingSession = false;
0443     return true;
0444 }
0445 
0446 void KisPart::slotDocumentSaved(const QString &filePath)
0447 {
0448     // We used to use doc->path(), but it does not contain the correct output
0449     // file path when doing an export, therefore we now pass it directly from
0450     // the sigSavingFinished signal.
0451     // KisDocument *doc = qobject_cast<KisDocument*>(sender());
0452     emit sigDocumentSaved(filePath);
0453 
0454     QUrl url = QUrl::fromLocalFile(filePath);
0455     KisRecentFileIconCache::instance()->reloadFileIcon(url);
0456     if (!d->pendingAddRecentUrlMap.contains(url)) {
0457         return;
0458     }
0459     QUrl oldUrl = d->pendingAddRecentUrlMap.take(url);
0460     addRecentURLToAllMainWindows(url, oldUrl);
0461 }
0462 
0463 void KisPart::removeMainWindow(KisMainWindow *mainWindow)
0464 {
0465     dbgUI <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this;
0466     if (mainWindow) {
0467         d->mainWindows.removeAll(mainWindow);
0468     }
0469 }
0470 
0471 const QList<QPointer<KisMainWindow> > &KisPart::mainWindows() const
0472 {
0473     return d->mainWindows;
0474 }
0475 
0476 int KisPart::mainwindowCount() const
0477 {
0478     return d->mainWindows.count();
0479 }
0480 
0481 
0482 KisMainWindow *KisPart::currentMainwindow() const
0483 {
0484     QWidget *widget = qApp->activeWindow();
0485     KisMainWindow *mainWindow = qobject_cast<KisMainWindow*>(widget);
0486     while (!mainWindow && widget) {
0487         widget = widget->parentWidget();
0488         mainWindow = qobject_cast<KisMainWindow*>(widget);
0489     }
0490 
0491     if (!mainWindow && mainWindows().size() > 0) {
0492         mainWindow = mainWindows().first();
0493     }
0494     return mainWindow;
0495 
0496 }
0497 
0498 QWidget *KisPart::currentMainwindowAsQWidget() const
0499 {
0500     return currentMainwindow();
0501 }
0502 
0503 KisMainWindow * KisPart::windowById(QUuid id) const
0504 {
0505     Q_FOREACH(QPointer<KisMainWindow> mainWindow, d->mainWindows) {
0506         if (mainWindow->id() == id) {
0507             return mainWindow;
0508         }
0509     }
0510 
0511     return nullptr;
0512 }
0513 
0514 KisIdleWatcher* KisPart::idleWatcher() const
0515 {
0516     return &d->idleWatcher;
0517 }
0518 
0519 KisAnimationCachePopulator* KisPart::cachePopulator() const
0520 {
0521     return &d->animationCachePopulator;
0522 }
0523 
0524 KisPlaybackEngine *KisPart::playbackEngine() const
0525 {
0526     return d->playbackEngine.data();
0527 }
0528 
0529 void KisPart::prioritizeFrameForCache(KisImageSP image, int frame) {
0530     KisImageAnimationInterface* animInterface = image->animationInterface();
0531     if ( animInterface && animInterface->documentPlaybackRange().contains(frame)) {
0532         d->animationCachePopulator.requestRegenerationWithPriorityFrame(image, frame);
0533     }
0534 }
0535 
0536 void KisPart::openExistingFile(const QString &path)
0537 {
0538     // TODO: refactor out this method!
0539 
0540     KisMainWindow *mw = currentMainwindow();
0541     KIS_SAFE_ASSERT_RECOVER_RETURN(mw);
0542 
0543     mw->openDocument(path, KisMainWindow::None);
0544 }
0545 
0546 void KisPart::updateShortcuts()
0547 {
0548     // Update the UI actions. KisActionRegistry also takes care of updating
0549     // shortcut hints in tooltips.
0550     Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) {
0551         KisKActionCollection *ac = mainWindow->actionCollection();
0552 
0553         ac->updateShortcuts();
0554     }
0555 }
0556 
0557 void KisPart::openTemplate(const QUrl &url)
0558 {
0559     KisCursorOverrideLock cursorLock(Qt::BusyCursor);
0560 
0561     KisDocument *document = createDocument();
0562 
0563     bool ok = document->loadNativeFormat(url.toLocalFile());
0564     document->setModified(false);
0565     document->undoStack()->clear();
0566     document->documentInfo()->resetMetaData();
0567 
0568     if (ok) {
0569         QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile());
0570         // in case this is a open document template remove the -template from the end
0571         mimeType.remove( QRegExp( "-template$" ) );
0572         document->setMimeTypeAfterLoading(mimeType);
0573         document->resetPath();
0574         document->setReadWrite(true);
0575     }
0576     else {
0577         if (document->errorMessage().isEmpty()) {
0578             QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1", document->localFilePath()));
0579         }
0580         else {
0581             QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1\nReason: %2", document->localFilePath(), document->errorMessage()));
0582         }
0583         delete document;
0584         return;
0585     }
0586     QMap<QString, QString> dictionary;
0587     // XXX: fill the dictionary from the desktop file
0588     KisTranslateLayerNamesVisitor v(dictionary);
0589     document->image()->rootLayer()->accept(v);
0590 
0591     addDocument(document);
0592 
0593     KisMainWindow *mw = currentMainwindow();
0594     mw->addViewAndNotifyLoadingCompleted(document);
0595 }
0596 
0597 void KisPart::addRecentURLToAllMainWindows(QUrl url, QUrl oldUrl)
0598 {
0599     // Add entry to recent documents list
0600     // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.)
0601     if (!url.isEmpty()) {
0602         bool ok = true;
0603         if (url.isLocalFile()) {
0604             QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
0605             const QStringList tmpDirs = QStandardPaths::locateAll(QStandardPaths::TempLocation, "", QStandardPaths::LocateDirectory);
0606             for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) {
0607                 if (path.contains(*it)) {
0608                     ok = false; // it's in the tmp resource
0609                 }
0610             }
0611 
0612             const QStringList templateDirs = KoResourcePaths::findDirs("templates");
0613             for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) {
0614                 if (path.contains(*it)) {
0615                     ok = false; // it's in the templates directory.
0616                     break;
0617                 }
0618             }
0619         }
0620         if (ok) {
0621             if (!oldUrl.isEmpty()) {
0622                 KisRecentFilesManager::instance()->remove(oldUrl);
0623             }
0624             KisRecentFilesManager::instance()->add(url);
0625         }
0626     }
0627 }
0628 
0629 void KisPart::queueAddRecentURLToAllMainWindowsOnFileSaved(QUrl url, QUrl oldUrl)
0630 {
0631     d->pendingAddRecentUrlMap.insert(url, oldUrl);
0632 }
0633 
0634 void KisPart::startCustomDocument(KisDocument* doc)
0635 {
0636     addDocument(doc);
0637     KisMainWindow *mw = currentMainwindow();
0638     mw->addViewAndNotifyLoadingCompleted(doc);
0639 
0640 }
0641 
0642 KisInputManager* KisPart::currentInputManager()
0643 {
0644     KisMainWindow *mw = currentMainwindow();
0645     KisViewManager *manager = mw ? mw->viewManager() : 0;
0646     return manager ? manager->inputManager() : 0;
0647 }
0648 
0649 void KisPart::showSessionManager()
0650 {
0651     if (d->sessionManager.isNull()) {
0652         d->sessionManager.reset(new KisSessionManagerDialog());
0653     }
0654 
0655     d->sessionManager->show();
0656     d->sessionManager->activateWindow();
0657 }
0658 
0659 void KisPart::startBlankSession()
0660 {
0661     KisMainWindow *window = createMainWindow();
0662     window->initializeGeometry();
0663     window->show();
0664 
0665 }
0666 
0667 bool KisPart::restoreSession(const QString &sessionName)
0668 {
0669     if (sessionName.isNull()) return false;
0670 
0671     KoResourceServer<KisSessionResource> *rserver = KisResourceServerProvider::instance()->sessionServer();
0672     KisSessionResourceSP session = rserver->resource("", "", sessionName);
0673     if (!session || !session->valid()) return false;
0674 
0675     return restoreSession(session);
0676 }
0677 
0678 bool KisPart::restoreSession(KisSessionResourceSP session)
0679 {
0680     session->restore();
0681     d->currentSession = session;
0682     return true;
0683 }
0684 
0685 void KisPart::setCurrentSession(KisSessionResourceSP session)
0686 {
0687     d->currentSession = session;
0688 }
0689 
0690 void KisPart::upgradeToPlaybackEngineMLT(KoCanvasBase* canvas)
0691 {
0692 #ifdef HAVE_MLT
0693 
0694     // TODO: This is a slightly hacky workaround to loading the MLT engine over itself,
0695     // as the QT-based engine no longer supports audio. Is there a better way?
0696     if (d->playbackEngine->supportsAudio()) {
0697         return;
0698     }
0699 
0700     setPlaybackEngine(new KisPlaybackEngineMLT(this));
0701 
0702     if (canvas) {
0703         d->playbackEngine->setObservedCanvas(canvas);
0704     }
0705 
0706 #endif //HAVE_MLT
0707 }
0708 
0709 void KisPart::setPlaybackEngine(KisPlaybackEngine *p_playbackEngine)
0710 {
0711     // make sure that the old engine is still alive until the end
0712     // of the emitted signal
0713     QScopedPointer backup(p_playbackEngine);
0714     d->playbackEngine.swap(backup);
0715 
0716     emit playbackEngineChanged(p_playbackEngine);
0717 }
0718 
0719 #include "moc_KisPart.cpp"